  

/*-------------------------------------------------------------------------
 *
 * nodeAgg.c
 *	  处理聚合节点的例程。
 *
 *	  ExecAgg 通常按以下步骤评估每个聚合：
 *
 *		 transvalue = initcond
 *		 对于每个 input_tuple 执行
 *			transvalue = transfunc(transvalue, input_value(s))
 *		 result = finalfunc(transvalue, direct_argument(s))
 *
 *	  如果没有提供 finalfunc，则结果就是 transvalue 的结束值。
 *
 *	  "aggsplit" 模式可以选择其他行为，用于支持部分聚合。可以：
 *	  * 跳过运行 finalfunc，使输出始终是最终 transvalue 状态。
 *	  * 用 combinefunc 替代 transfunc，使得从子部分聚合步骤传播上来的 transvalue 状态被合并，而不是处理原始输入行。（以下关于 transfunc 的声明同样适用于选择 combinefunc 时。）
 *	  * 对输出值应用 serializefunc （这只有在跳过 finalfunc 时才有意义，因为 serializefunc 对 transvalue 数据类型有效）。
 *	  * 对输入值应用 deserializefunc （这只有在使用 combinefunc 时才有意义，原因类似）。
 *	  规划器的责任是以合理的方式连接 Agg 节点，使用这些替代行为，将部分聚合结果传递给期望它们的节点。
 *
 *	  如果普通聚合调用指定了 DISTINCT 或 ORDER BY，我们会在执行上述过程之前对输入元组进行排序并消除重复（如果需要的话）。（然而，对于有序集合聚合，我们不这样做；就本模块而言，它们的 "ORDER BY" 输入是普通聚合参数。）注意，在这些情况下不支持部分聚合，因为我们无法确保输入的全局排序或唯一性。
 *
 *	  如果 transfunc 在 pg_proc 中被标记为 "strict" 且 initcond 为 NULL，则第一个非 NULL input_value 会直接分配给 transvalue，而 transfunc 直到第二个非 NULL input_value 时才应用。
 *	  在这种情况下，agg 的第一个输入类型和 transtype 必须相同！
 *
 *	  如果 transfunc 被标记为 "strict"，则 NULL input_values 会被跳过，保持之前的 transvalue。如果 transfunc 不是严格的，则会对每个输入元组调用它，并且必须自行处理 NULL initcond 或 NULL input_values。
 *
 *	  如果 finalfunc 被标记为 "strict"，则当结束 transvalue 为 NULL 时不会调用它，而是自动创建一个 NULL 结果（这当然是对严格函数的通常处理）。非严格 finalfunc 可以自行选择对于 NULL 结束 transvalue 返回什么。
 *
 *	  有序集合聚合在另一种方式上受到特殊处理：我们评估任何 "direct" 参数，并将其与过渡值一起传递给 finalfunc。
 *
 *	  finalfunc 可以有额外的参数，超出 transvalue 和任何 "direct" 参数，用于对应于聚合的输入参数。这些参数始终传递为 NULL。这些参数可能是为了解决多态聚合的结果类型而需要的。
 *
 *	  我们计算聚合输入表达式，并在一个临时的 econtext 中运行过渡函数（aggstate->tmpcontext）。每个输入元组至少重置一次，因此当 transvalue 数据类型通过引用传递时，我们必须小心将其复制到一个生命周期更长的内存上下文中，并释放之前的值以避免内存泄漏。我们将 transvalues 存储在另一组 econtexts 中，aggstate->aggcontexts（每个分组集一个，见下文），这些也用于 AGG_HASHED 模式中的哈希表结构。这些 econtexts 在组边界处重新扫描，而不仅仅是重置，以便聚合过渡函数可以通过 AggRegisterCallback 注册关闭回调。
 *
 *	  节点的常规 econtext（aggstate->ss.ps.ps_ExprContext）用于运行最终化函数和计算输出元组；此上下文可以在每个输出元组中重置一次。
 *
 *	  执行器的 AggState 节点在所有 transfunc 和 finalfunc 调用中作为 fmgr 的 "context" 值传递。建议过渡函数不要直接查看 AggState 节点，但可以使用 AggCheckCallContext() 验证它们是否被 nodeAgg.c 调用（而不是作为普通 SQL 函数）。过渡函数想要知道这一点的主要原因是为了避免在每次调用时 palloc_fixed-size pass-by-ref 过渡值：它可以简单地在其左输入上书写并返回。通常完全禁止函数修改 pass-by-ref 输入，但在聚合情况下我们知道左输入可以是初始过渡值或先前函数结果，在这两种情况下，它的值不需要被保留。请参阅 int8inc() 了解示例。请注意，EEOP_AGG_PLAIN_TRANS 步骤的编码旨在避免在返回之前的过渡值指针时进行数据复制。当过渡值是扩展对象时，也可以避免重复的数据复制：为此，过渡函数必须小心返回一个在 AggCheckCallContext() 返回的内存上下文的子上下文中的扩展对象。此外，一些过渡函数希望除了名义上的过渡值外，还存储工作状态；它们可以使用 AggCheckCallContext() 返回的内存上下文来执行此操作。
 *
 *	  注意：AggCheckCallContext() 从 PostgreSQL 9.0 开始可用。在早期版本中（直到 8.1），AggState 可作为上下文使用，但在 9.0 之前使用它需要直接检查节点。
 *
 *	  从 9.4 开始，聚合过渡函数还可以使用 AggGetAggref() 获取其聚合调用的 Aggref 表达式节点。这主要用于有序集合聚合，这些聚合作为窗口函数不受支持。（常规聚合函数需要一些后备逻辑来使用这个，因为窗口函数没有 Aggref 节点。）
 *
 *	  分组集：
 *
 *	  一组分组集结构上等同于 ROLLUP 子句（例如 (a,b,c), (a,b), (a)）可以在有序数据上进行单次处理。我们通过保持针对每个正在并发处理的分组集的一组独立过渡值来实现这一点；对于每个输入元组，我们更新它们所有，并在组边界上重置那些其分组值已更改的状态（分组集的列表按从最具体到最不具体的顺序排列）。
 *
 *	  当使用更复杂的分组集时，我们将其分解为 "阶段"，每个阶段具有不同的排序顺序（第 0 阶段除外，保留用于哈希）。在每个阶段，但最后一个阶段，输入元组还被存储在一个元组排序中，该排序是针对下一个阶段的排序顺序进行键控的；在每个阶段，但第一个阶段，输入元组是从先前排序的数据中提取的。（第一个阶段数据的排序由规划器处理，因为它可能由底层节点满足。）
 *
 *	  哈希可以与排序分组混合。为此，我们有一个 AGG_MIXED 策略，在第一个排序阶段期间填充哈希表，并在完成所有排序阶段后切换到读取它们。我们还可以支持 AGG_HASHED，可使用多个哈希表且完全不排序。
 *
 *	  从聚合过渡和最终函数的角度来看，关于分组集的唯一问题是：一个聚合函数的单个调用站点（flinfo）可能被用于依次更新几个不同的过渡值。因此，函数不能在 flinfo 中缓存逻辑上属于过渡值的任何内容（最重要的是，过渡值存在的内存上下文）。支持 API 函数（AggCheckCallContext，AggRegisterCallback）对当前正在调用聚合函数的分组集敏感。
 *
 *	  计划结构：
 *
 *	  我们从规划器获得的实际上是一个 "真实" 的 Agg 节点，它是计划树的一部分，但可以选择通过 "chain" 字段挂靠在旁边的附加 Agg 节点列表。这是因为 Agg 节点恰好是我们处理分组集所需的所有数据的方便表示。
 *
 *	  在许多目的上，我们将 "真实" 节点视作它只是链中的第一个节点。链必须按顺序排列，使哈希条目在排序/普通条目之前；如果存在两种类型，则真实节点标记为 AGG_MIXED（在这种情况下，真实节点描述其中一种哈希分组，其他 AGG_HASHED 节点可以选择跟随在链中，接着是 AGG_SORTED 或（一个）AGG_PLAIN 节点）。如果真实节点标记为 AGG_HASHED 或 AGG_SORTED，则所有链节点必须为相同类型；如果为 AGG_PLAIN，则不能有链节点。
 *
 *	  我们将所有哈希节点收集到单个 "阶段" 中，编号为 0，并为每个 AGG_SORTED 或 AGG_PLAIN 节点创建一个排序阶段（编号为 1..n）。即使没有哈希，仍然分配阶段 0，但在这种情况下保持未使用。
 *
 *	  AGG_HASHED 节点实际上只引用每个单一分组集，因为对于每个哈希分组，我们需要单独的 grpColIdx 和 numGroups 估计。AGG_SORTED 节点表示 "ROLLUP"，即共享排序顺序的分组集列表。除了第一个 AGG_SORTED 节点外的每个 AGG_SORTED 节点都有一个与之关联的 Sort 节点，描述要使用的排序顺序；第一个已排序节点从外部子树中获取其输入，规划器已经安排好提供有序数据。
 *
 *	  内存和 ExprContext 使用：
 *
 *	  因为我们在输入行中累积聚合值，所以需要使用比简单输入/输出元组上下文更多的内存上下文。实际上，对于 ROLLUP，我们需要为每个分组集创建一个单独的上下文，以便我们可以在其组边界上重置内部（更细粒度的）聚合，同时继续为外部（较粗粒度的）分组累积值。在此基础上，我们可能同时填充哈希表；但是，我们只需要一个上下文来支持所有哈希表。
 *
 *	  因此，我们创建一个数组，aggcontexts，为我们要处理的最大 ROLLUP 中的每个分组集创建一个 ExprContext，并使用这些 ExprContexts 的每元组内存上下文来存储聚合过渡值。hashcontext 是为支持所有哈希表而创建的单个上下文。
 *
 *	  溢出到磁盘
 *
 *	  在执行哈希聚合时，如果哈希表内存超过限制（见 hash_agg_check_limits()），我们进入 "溢出模式"。在溢出模式中，我们仅提前向已在哈希表中的组推进过渡状态。对于需要创建新的哈希表条目（并初始化新的过渡状态）的元组，我们会将它们溢出到磁盘，以便稍后处理。元组以分区方式溢出，这样后续批次更小并且不太可能超过 hash_mem（如果一个批次确实超过 hash_mem，它必须递归溢出）。
 *
 *	  溢出的数据写入逻辑磁带。这些与使用 BufFile 为每次溢出提供比内存使用、磁盘空间和文件数更好的控制。我们在算法开始时不知道所需的磁带数量（因为它可以递归），因此在开始时分配一个磁带集，并根据需要创建单独的磁带。在读取特定磁带时，logtape.c 循环利用其磁盘空间。当磁带读取完成时，它会被完全销毁。
 *
 *	  当同时打开多个磁带时，磁带的缓冲区可以占用大量内存。我们一次只需要打开一条磁带用于读取模式（使用一个大小为 BLCKSZ 的缓冲区的倍数）；但是，我们需要在写模式下为每个分区打开一条磁带（每个需要一个大小为 BLCKSZ 的缓冲区）。
 *
 *	  注意，过渡状态可能开始时较小，但随即变得非常大；例如，在 ARRAY_AGG 的情况下。在这种情况下，仍然可能显著超过 hash_mem。我们尝试通过估计将适合可用内存的内容，并对组的数量施加限制，从而避免这种情况。
 *
 *    过渡/组合函数调用：
 *
 *    出于性能原因，过渡函数，包括组合函数，并不是在通过表达式评估引擎计算参数后由 nodeAgg.c 一一调用。相反，ExecBuildAggTrans() 构建一个大型表达式，该表达式同时完成参数评估和过渡函数调用。这避免了因重复使用表达式评估而导致的性能问题、过滤表达式必须提前评估所带来的复杂性，并允许将整个表达式即时编译为一个本机函数。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/executor/nodeAgg.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/htup_details.h"
#include "access/parallel.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
#include "executor/execExpr.h"
#include "executor/executor.h"
#include "executor/nodeAgg.h"
#include "lib/hyperloglog.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parse_agg.h"
#include "parser/parse_coerce.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/dynahash.h"
#include "utils/expandeddatum.h"
#include "utils/logtape.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
#include "utils/tuplesort.h"

/*
 * 控制溢出 HashAgg 到磁盘时创建的分区数量。
 *
 * HASHAGG_PARTITION_FACTOR 乘以估计所需的分区数量，以确保每个分区可以适应内存。这个因子设置得大于1，因为拥有一些过多的分区没有高昂的成本，这使得分区需要递归溢出的可能性较小。拥有更多更小的分区的另一个好处是，由于内存缓存效应，小哈希表可能比大哈希表性能更好。
 *
 * 我们还指定了每次溢出的最小和最大分区数量。太少可能意味着重复溢出相同元组会浪费大量I/O。太多则会导致大量内存浪费在缓冲溢出文件上（这本可以用于更大的哈希表）。
 */
#define HASHAGG_PARTITION_FACTOR 1.50
#define HASHAGG_MIN_PARTITIONS 4
#define HASHAGG_MAX_PARTITIONS 1024

/*
 * 读取磁带时，缓冲区大小必须是 BLCKSZ 的倍数。较大的值在同时从多条磁带读取时有帮助，但在 HashAgg 中并不会发生，因此我们简单地使用 BLCKSZ。写入磁带时始终使用大小为 BLCKSZ 的缓冲区。
 */
#define HASHAGG_READ_BUFFER_SIZE BLCKSZ
#define HASHAGG_WRITE_BUFFER_SIZE BLCKSZ

/*
 * HyperLogLog 用于估计给定分区中溢出元组的基数。5 位对应大约 32 字节的大小和大约 18% 的最坏情况误差。对于选择合理数量的分区在递归时足够有效。
 */
#define HASHAGG_HLL_BIT_WIDTH 5

/*
 * 估计块开销为恒定的 16 字节。XXX：这应该改善吗？
 */
#define CHUNKHDRSZ 16

/*
 * 表示单个哈希表的分区溢出数据。包含将元组路由到正确分区所需的信息，以及将溢出数据转换为新批次的所需信息。
 *
 * 高位用于分区选择（当递归时，我们忽略在早期级别已经用于分区选择的位）。
 */
typedef struct HashAggSpill
{
	int			npartitions;	/* 分区数量 */
	LogicalTape **partitions;	/* 溢出分区磁带 */
	int64	   *ntuples;		/* 每个分区中的元组数量 */
	uint32		mask;			/* 从哈希值查找分区的掩码 */
	int			shift;			/* 掩码后，按此量移动 */
	hyperLogLogState *hll_card; /* 内容的基数估计 */
} HashAggSpill;

/*
 * 表示针对单次哈希聚合（仅一个分组集）要完成的工作。
 *
 * 还跟踪早期迭代已用于分区选择的哈希位，以便此批次可以使用新的位。如果所有位都已使用，则不会进行分区（任何溢出数据将发送到单个输出磁带）。
 */
typedef struct HashAggBatch
{
	int			setno;			/* 分组集 */
	int			used_bits;		/* 已使用的哈希位数 */
	LogicalTape *input_tape;	/* 输入分区磁带 */
	int64		input_tuples;	/* 此批次中的元组数量 */
	double		input_card;		/* 估计的组基数 */
} HashAggBatch;

/* 用于查找引用的列号 */
typedef struct FindColsContext
{
	bool		is_aggref;		/* 在聚合引用下 */
	Bitmapset  *aggregated;		/* 聚合引用下的列 */
	Bitmapset  *unaggregated;	/* 其他列引用 */
} FindColsContext;

static void fc_select_current_set(AggState *fc_aggstate, int fc_setno, bool fc_is_hash);
static void fc_initialize_phase(AggState *fc_aggstate, int fc_newphase);
static TupleTableSlot *fc_fetch_input_tuple(AggState *fc_aggstate);
static void fc_initialize_aggregates(AggState *fc_aggstate,
								  AggStatePerGroup *fc_pergroups,
								  int fc_numReset);
static void fc_advance_transition_function(AggState *fc_aggstate,
										AggStatePerTrans fc_pertrans,
										AggStatePerGroup fc_pergroupstate);
static void fc_advance_aggregates(AggState *fc_aggstate);
static void fc_process_ordered_aggregate_single(AggState *fc_aggstate,
											 AggStatePerTrans fc_pertrans,
											 AggStatePerGroup fc_pergroupstate);
static void fc_process_ordered_aggregate_multi(AggState *fc_aggstate,
											AggStatePerTrans fc_pertrans,
											AggStatePerGroup fc_pergroupstate);
static void fc_finalize_aggregate(AggState *fc_aggstate,
							   AggStatePerAgg fc_peragg,
							   AggStatePerGroup fc_pergroupstate,
							   Datum *fc_resultVal, bool *fc_resultIsNull);
static void fc_finalize_partialaggregate(AggState *fc_aggstate,
									  AggStatePerAgg fc_peragg,
									  AggStatePerGroup fc_pergroupstate,
									  Datum *fc_resultVal, bool *fc_resultIsNull);
static inline void fc_prepare_hash_slot(AggStatePerHash fc_perhash,
									 TupleTableSlot *fc_inputslot,
									 TupleTableSlot *fc_hashslot);
static void fc_prepare_projection_slot(AggState *fc_aggstate,
									TupleTableSlot *fc_slot,
									int fc_currentSet);
static void fc_finalize_aggregates(AggState *fc_aggstate,
								AggStatePerAgg fc_peragg,
								AggStatePerGroup fc_pergroup);
static TupleTableSlot *fc_project_aggregates(AggState *fc_aggstate);
static void fc_find_cols(AggState *fc_aggstate, Bitmapset **fc_aggregated,
					  Bitmapset **fc_unaggregated);
static bool fc_find_cols_walker(Node *fc_node, FindColsContext *fc_context);
static void fc_build_hash_tables(AggState *fc_aggstate);
static void fc_build_hash_table(AggState *fc_aggstate, int fc_setno, long fc_nbuckets);
static void fc_hashagg_recompile_expressions(AggState *fc_aggstate, bool fc_minslot,
										  bool fc_nullcheck);
static long fc_hash_choose_num_buckets(double fc_hashentrysize,
									long fc_estimated_nbuckets,
									Size fc_memory);
static int	fc_hash_choose_num_partitions(double fc_input_groups,
									   double fc_hashentrysize,
									   int fc_used_bits,
									   int *fc_log2_npartittions);
static void fc_initialize_hash_entry(AggState *fc_aggstate,
								  TupleHashTable hashtable,
								  TupleHashEntry fc_entry);
static void fc_lookup_hash_entries(AggState *fc_aggstate);
static TupleTableSlot *fc_agg_retrieve_direct(AggState *fc_aggstate);
static void fc_agg_fill_hash_table(AggState *fc_aggstate);
static bool fc_agg_refill_hash_table(AggState *fc_aggstate);
static TupleTableSlot *fc_agg_retrieve_hash_table(AggState *fc_aggstate);
static TupleTableSlot *fc_agg_retrieve_hash_table_in_memory(AggState *fc_aggstate);
static void fc_hash_agg_check_limits(AggState *fc_aggstate);
static void fc_hash_agg_enter_spill_mode(AggState *fc_aggstate);
static void fc_hash_agg_update_metrics(AggState *fc_aggstate, bool fc_from_tape,
									int fc_npartitions);
static void fc_hashagg_finish_initial_spills(AggState *fc_aggstate);
static void fc_hashagg_reset_spill_state(AggState *fc_aggstate);
static HashAggBatch *fc_hashagg_batch_new(LogicalTape *fc_input_tape, int fc_setno,
									   int64 fc_input_tuples, double fc_input_card,
									   int fc_used_bits);
static MinimalTuple fc_hashagg_batch_read(HashAggBatch *fc_batch, uint32 *fc_hashp);
static void fc_hashagg_spill_init(HashAggSpill *fc_spill, LogicalTapeSet *fc_lts,
							   int fc_used_bits, double fc_input_groups,
							   double fc_hashentrysize);
static Size fc_hashagg_spill_tuple(AggState *fc_aggstate, HashAggSpill *fc_spill,
								TupleTableSlot *fc_slot, uint32 fc_hash);
static void fc_hashagg_spill_finish(AggState *fc_aggstate, HashAggSpill *fc_spill,
								 int fc_setno);
static Datum fc_GetAggInitVal(Datum fc_textInitVal, Oid fc_transtype);
static void fc_build_pertrans_for_aggref(AggStatePerTrans fc_pertrans,
									  AggState *fc_aggstate, EState *fc_estate,
									  Aggref *fc_aggref, Oid fc_transfn_oid,
									  Oid fc_aggtranstype, Oid fc_aggserialfn,
									  Oid fc_aggdeserialfn, Datum fc_initValue,
									  bool fc_initValueIsNull, Oid *fc_inputTypes,
									  int fc_numArguments);


/*
 * 选择当前分组集；影响 current_set 和 curaggcontext。
 */
static void fc_select_current_set(AggState *fc_aggstate, int fc_setno, bool fc_is_hash)
{
	/*
	 * 当更改此项时，还要调整 ExecAggPlainTransByVal() 和 ExecAggPlainTransByRef()。
	 */
	if (fc_is_hash)
		fc_aggstate->curaggcontext = fc_aggstate->hashcontext;
	else
		fc_aggstate->curaggcontext = fc_aggstate->aggcontexts[fc_setno];

	fc_aggstate->current_set = fc_setno;
}

/*
 * 切换到 "newphase" 阶段，必须是 0 或 1（重置）或 current_phase + 1。相应地调整元组排序。
 *
 * 阶段 0 用于哈希，这在 AGG_MIXED 情况下我们当前最后处理，因此当进入阶段 0 时，我们所需的只是丢弃打开的排序。
 */
static void fc_initialize_phase(AggState *fc_aggstate, int fc_newphase)
{
	Assert(fc_newphase <= 1 || fc_newphase == fc_aggstate->current_phase + 1);

	/*
	 * 无论之前的状态如何，我们现在已经完成了任何正在使用的输入元组排序。
	 */
	if (fc_aggstate->sort_in)
	{
		tuplesort_end(fc_aggstate->sort_in);
		fc_aggstate->sort_in = NULL;
	}

	if (fc_newphase <= 1)
	{
		/*
		 * 丢弃任何现有的输出tuplesort。
		 */
		if (fc_aggstate->sort_out)
		{
			tuplesort_end(fc_aggstate->sort_out);
			fc_aggstate->sort_out = NULL;
		}
	}
	else
	{
		/*
		 * 旧的输出元组排序变成新的输入元组，现在是对其进行排序的正确时机。
		 */
		fc_aggstate->sort_in = fc_aggstate->sort_out;
		fc_aggstate->sort_out = NULL;
		Assert(fc_aggstate->sort_in);
		tuplesort_performsort(fc_aggstate->sort_in);
	}

	/*
	 * 如果这不是最后一个阶段，我们需要为下一个阶段按顺序适当排序。
	 */
	if (fc_newphase > 0 && fc_newphase < fc_aggstate->numphases - 1)
	{
		Sort	   *fc_sortnode = fc_aggstate->phases[fc_newphase + 1].sortnode;
		PlanState  *fc_outerNode = outerPlanState(fc_aggstate);
		TupleDesc	fc_tupDesc = ExecGetResultType(fc_outerNode);

		fc_aggstate->sort_out = tuplesort_begin_heap(fc_tupDesc,
												  fc_sortnode->numCols,
												  fc_sortnode->sortColIdx,
												  fc_sortnode->sortOperators,
												  fc_sortnode->collations,
												  fc_sortnode->nullsFirst,
												  work_mem,
												  NULL, TUPLESORT_NONE);
	}

	fc_aggstate->current_phase = fc_newphase;
	fc_aggstate->phase = &fc_aggstate->phases[fc_newphase];
}

/*
 * 从外部计划（对于阶段1）或从前一阶段填充的排序器中获取元组。 如果有，下一个阶段将其复制到排序器中。
 *
 * 调用者不能依赖返回插槽中的元组的内存在任何随后获取的元组后保持有效。
 */
static TupleTableSlot * fc_fetch_input_tuple(AggState *fc_aggstate)
{
	TupleTableSlot *fc_slot;

	if (fc_aggstate->sort_in)
	{
		/* 确保我们在此路径中检查中断 */
		CHECK_FOR_INTERRUPTS();
		if (!tuplesort_gettupleslot(fc_aggstate->sort_in, true, false,
									fc_aggstate->sort_slot, NULL))
			return NULL;
		fc_slot = fc_aggstate->sort_slot;
	}
	else
		fc_slot = ExecProcNode(outerPlanState(fc_aggstate));

	if (!TupIsNull(fc_slot) && fc_aggstate->sort_out)
		tuplesort_puttupleslot(fc_aggstate->sort_out, fc_slot);

	return fc_slot;
}

/*
 * （重新）初始化单个聚合。
 *
 * 此函数仅处理一个分组集，已在 aggstate->current_set 中设置。
 *
 * 调用时，CurrentMemoryContext 应为每查询上下文。
 */
static void fc_initialize_aggregate(AggState *fc_aggstate, AggStatePerTrans fc_pertrans,
					 AggStatePerGroup fc_pergroupstate)
{
	/*
	 * 每个 DISTINCT/ORDER BY 聚合开始一次新的排序操作。
	 */
	if (fc_pertrans->numSortCols > 0)
	{
		/*
		 * 在重新扫描的情况下，可能会有未完成的排序操作？ 如果是这样，将其清理。
		 */
		if (fc_pertrans->sortstates[fc_aggstate->current_set])
			tuplesort_end(fc_pertrans->sortstates[fc_aggstate->current_set]);


		/*
		 * 当只有一个输入列时，我们使用普通的 Datum 排序器； 
		 * 否则对整个元组进行排序。（请参阅关于 process_ordered_aggregate_single 的注释。）
		 */
		if (fc_pertrans->numInputs == 1)
		{
			Form_pg_attribute fc_attr = TupleDescAttr(fc_pertrans->sortdesc, 0);

			fc_pertrans->sortstates[fc_aggstate->current_set] =
				tuplesort_begin_datum(fc_attr->atttypid,
									  fc_pertrans->sortOperators[0],
									  fc_pertrans->sortCollations[0],
									  fc_pertrans->sortNullsFirst[0],
									  work_mem, NULL, TUPLESORT_NONE);
		}
		else
			fc_pertrans->sortstates[fc_aggstate->current_set] =
				tuplesort_begin_heap(fc_pertrans->sortdesc,
									 fc_pertrans->numSortCols,
									 fc_pertrans->sortColIdx,
									 fc_pertrans->sortOperators,
									 fc_pertrans->sortCollations,
									 fc_pertrans->sortNullsFirst,
									 work_mem, NULL, TUPLESORT_NONE);
	}

	/*
	 * （重新）将 transValue 设置为初始值。
	 *
	 * 注意，当初始值是按引用传递时，我们必须将其复制（到 aggcontext），
	 * 因为稍后我们将释放 transValue。
	 */
	if (fc_pertrans->initValueIsNull)
		fc_pergroupstate->transValue = fc_pertrans->initValue;
	else
	{
		MemoryContext fc_oldContext;

		fc_oldContext = MemoryContextSwitchTo(fc_aggstate->curaggcontext->ecxt_per_tuple_memory);
		fc_pergroupstate->transValue = datumCopy(fc_pertrans->initValue,
											  fc_pertrans->transtypeByVal,
											  fc_pertrans->transtypeLen);
		MemoryContextSwitchTo(fc_oldContext);
	}
	fc_pergroupstate->transValueIsNull = fc_pertrans->initValueIsNull;

	/*
	 * 如果过渡状态的初始值在 pg_aggregate 表中不存在，那么我们将让外部 procNode 返回的第一个非 NULL 值成为初始值。
	 * （这对于 max() 和 min() 等聚合很有用。）noTransValue 标志表明我们仍然需要执行此操作。
	 */
	fc_pergroupstate->noTransValue = fc_pertrans->initValueIsNull;
}

/*
 * 为新一组输入值初始化所有聚合过渡状态。
 *
 * 如果有多个分组集，我们仅初始化前 numReset 个（分组集按顺序排列，以便最具体的那个是首个，且重置频率最高）。作为便利，
 * 如果 numReset 为 0，我们将重新初始化所有集。
 *
 * 注意：这不能用于哈希聚合，因为对于那些聚合，分组集编号必须从更上层指定。
 *
 * 调用时，CurrentMemoryContext 应为每查询上下文。
 */
static void fc_initialize_aggregates(AggState *fc_aggstate,
					  AggStatePerGroup *fc_pergroups,
					  int fc_numReset)
{
	int			fc_transno;
	int			fc_numGroupingSets = Max(fc_aggstate->phase->numsets, 1);
	int			fc_setno = 0;
	int			fc_numTrans = fc_aggstate->numtrans;
	AggStatePerTrans fc_transstates = fc_aggstate->pertrans;

	if (fc_numReset == 0)
		fc_numReset = fc_numGroupingSets;

	for (fc_setno = 0; fc_setno < fc_numReset; fc_setno++)
	{
		AggStatePerGroup fc_pergroup = fc_pergroups[fc_setno];

		fc_select_current_set(fc_aggstate, fc_setno, false);

		for (fc_transno = 0; fc_transno < fc_numTrans; fc_transno++)
		{
			AggStatePerTrans fc_pertrans = &fc_transstates[fc_transno];
			AggStatePerGroup fc_pergroupstate = &fc_pergroup[fc_transno];

			fc_initialize_aggregate(fc_aggstate, fc_pertrans, fc_pergroupstate);
		}
	}
}

/*
 * 给定新的输入值，仅在一个分组集中推进一个聚合状态的过渡函数（已经在 aggstate->current_set 中设置）
 *
 * 新值（和 null 标志）已预加载到位于 pertrans->transfn_fcinfo 中的参数位置 1 及以上，因此我们不需要再次复制它们以传递给过渡函数。
 * 我们也期望 fcinfo 的静态字段已经初始化；这是通过 ExecInitAgg() 完成的。
 *
 * 在哪个内存上下文中调用并不重要。
 */
static void fc_advance_transition_function(AggState *fc_aggstate,
							AggStatePerTrans fc_pertrans,
							AggStatePerGroup fc_pergroupstate)
{
	FunctionCallInfo fcinfo = fc_pertrans->transfn_fcinfo;
	MemoryContext fc_oldContext;
	Datum		fc_newVal;

	if (fc_pertrans->transfn.fn_strict)
	{
		/*
		 * 对于严格的 transfn，当输入为 NULL 时不发生任何操作；我们只需保持之前的 transValue。
		 */
		int			fc_numTransInputs = fc_pertrans->numTransInputs;
		int			fc_i;

		for (fc_i = 1; fc_i <= fc_numTransInputs; fc_i++)
		{
			if (fcinfo->args[fc_i].isnull)
				return;
		}
		if (fc_pergroupstate->noTransValue)
		{
			/*
			 * transValue 尚未初始化。这是第一个非 NULL 输入值。我们将其用作 transValue 的初始值。
			 * （我们已经检查了聚合的输入类型与其 transtype 是二进制兼容的，因此这里直接复制是可以的。）
			 *
			 * 如果是按引用传递，我们必须将 datum 复制到 aggcontext。我们不需要释放旧的 transValue，因为它是 NULL。
			 */
			fc_oldContext = MemoryContextSwitchTo(fc_aggstate->curaggcontext->ecxt_per_tuple_memory);
			fc_pergroupstate->transValue = datumCopy(fcinfo->args[1].value,
												  fc_pertrans->transtypeByVal,
												  fc_pertrans->transtypeLen);
			fc_pergroupstate->transValueIsNull = false;
			fc_pergroupstate->noTransValue = false;
			MemoryContextSwitchTo(fc_oldContext);
			return;
		}
		if (fc_pergroupstate->transValueIsNull)
		{
			/*
			 * 不要对 NULL 输入调用严格函数。请注意，如果 transfn 是严格的并且在先前周期返回了 NULL，可能会到达此处。
			 * 如果发生这种情况，我们将会将 NULL 传播到最后。
			 */
			return;
		}
	}

	/* 我们在每输入元组的内存上下文中运行过渡函数 */
	fc_oldContext = MemoryContextSwitchTo(fc_aggstate->tmpcontext->ecxt_per_tuple_memory);

	/* 为 AggGetAggref() 设置 aggstate->curpertrans */
	fc_aggstate->curpertrans = fc_pertrans;

	/*
	 * 可以调用过渡函数
	 */
	fcinfo->args[0].value = fc_pergroupstate->transValue;
	fcinfo->args[0].isnull = fc_pergroupstate->transValueIsNull;
	fcinfo->isnull = false;		/* 以防 transfn 不设置它 */

	fc_newVal = FunctionCallInvoke(fcinfo);

	fc_aggstate->curpertrans = NULL;

	/*
	 * 如果是按引用传递的数据类型，必须将新值复制到 aggcontext 中，并释放先前的 transValue。
	 * 但是如果 transfn 返回指向其第一个输入的指针，则我们无需执行任何操作。
	 * 此外，如果 transfn 返回指向已经是 aggcontext 子对象的 R/W 扩展对象的指针，则假设我们可以在不复制的情况下采用该值。
	 *
	 * 比较 newVal 和 pergroup->transValue 是安全的，无需考虑任一个为 NULL，因为 ExecAggTransReparent() 会确保在 NULL 时将 transValue 设置为 0。
	 * 否则，我们可能在 transValue 与 newValue 具有相同数值而不是 NULL 的情况下意外不重新归属。
	 * 这是一个相对热的路径，因此，不希望通过另一个分支解决这个通常情况，即过渡函数返回其（已修改的）输入参数。
	 */
	if (!fc_pertrans->transtypeByVal &&
		DatumGetPointer(fc_newVal) != DatumGetPointer(fc_pergroupstate->transValue))
		fc_newVal = ExecAggTransReparent(fc_aggstate, fc_pertrans,
									  fc_newVal, fcinfo->isnull,
									  fc_pergroupstate->transValue,
									  fc_pergroupstate->transValueIsNull);

	fc_pergroupstate->transValue = fc_newVal;
	fc_pergroupstate->transValueIsNull = fcinfo->isnull;

	MemoryContextSwitchTo(fc_oldContext);
}


/*
 * 将每个聚合转换状态推进一个输入元组。输入
 * 元组已存储在tmpcontext->ecxt_outertuple中，以便能被
 * ExecEvalExpr访问。
 *
 * 我们有两组转换状态需要处理：一组用于排序聚合，
 * 一组用于哈希聚合；我们在这里处理它们，以避免多次评估
 * 输入。
 *
 * 调用时，CurrentMemoryContext应该是每个查询的上下文。
 */
static void fc_advance_aggregates(AggState *fc_aggstate)
{
	bool		fc_dummynull;

	ExecEvalExprSwitchContext(fc_aggstate->phase->evaltrans,
							  fc_aggstate->tmpcontext,
							  &fc_dummynull);
}

/*
 * 针对只有一个输入的DISTINCT或ORDER BY聚合
 * 运行转换函数。这个函数在我们完成将所有
 * 输入值输入排序对象后被调用。我们完成
 * 排序，按照排序顺序读取值，并在每个值上运行
 * 转换函数（如果适用则应用DISTINCT）。
 *
 * 注意，转换函数的严格性在将值输入排序时
 * 已经被检查，所以我们在这里不再检查；
 * 我们只是应用标准的SQL DISTINCT逻辑。
 *
 * 单输入情况与多输入情况分开处理是出于性能考虑：
 * 对于单一值传递的输入，例如
 * count(distinct id)的常见情况，tuplesort_getdatum的代码路径
 * 快了大约300%。 （对于引用类型的加速则较少
 * 但仍然明显。）
 *
 * 此函数仅处理一个分组集（已在
 * aggstate->current_set中设置）。
 *
 * 调用时，CurrentMemoryContext应该是每个查询的上下文。
 */
static void fc_process_ordered_aggregate_single(AggState *fc_aggstate,
								 AggStatePerTrans fc_pertrans,
								 AggStatePerGroup fc_pergroupstate)
{
	Datum		fc_oldVal = (Datum) 0;
	bool		fc_oldIsNull = true;
	bool		fc_haveOldVal = false;
	MemoryContext fc_workcontext = fc_aggstate->tmpcontext->ecxt_per_tuple_memory;
	MemoryContext fc_oldContext;
	bool		fc_isDistinct = (fc_pertrans->numDistinctCols > 0);
	Datum		fc_newAbbrevVal = (Datum) 0;
	Datum		fc_oldAbbrevVal = (Datum) 0;
	FunctionCallInfo fcinfo = fc_pertrans->transfn_fcinfo;
	Datum	   *fc_newVal;
	bool	   *fc_isNull;

	Assert(fc_pertrans->numDistinctCols < 2);

	tuplesort_performsort(fc_pertrans->sortstates[fc_aggstate->current_set]);

	/* 将列加载到参数1（参数0将是过渡值） */
	fc_newVal = &fcinfo->args[1].value;
	fc_isNull = &fcinfo->args[1].isnull;

	/*
	 * 注意：如果输入类型是引用传递，排序返回的
	 * 数据项是在每个查询上下文中新分配的，因此我们必须
	 * 小心在不再需要它们时进行释放。
	 */

	while (tuplesort_getdatum(fc_pertrans->sortstates[fc_aggstate->current_set],
							  true, fc_newVal, fc_isNull, &fc_newAbbrevVal))
	{
		/*
		 * 清理并选择用于评估相等
		 * 函数和转换函数的工作上下文。
		 */
		MemoryContextReset(fc_workcontext);
		fc_oldContext = MemoryContextSwitchTo(fc_workcontext);

		/*
		 * 如果是DISTINCT模式，并且与先前的值不相等，则跳过。
		 */
		if (fc_isDistinct &&
			fc_haveOldVal &&
			((fc_oldIsNull && *fc_isNull) ||
			 (!fc_oldIsNull && !*fc_isNull &&
			  fc_oldAbbrevVal == fc_newAbbrevVal &&
			  DatumGetBool(FunctionCall2Coll(&fc_pertrans->equalfnOne,
											 fc_pertrans->aggCollation,
											 fc_oldVal, *fc_newVal)))))
		{
			/* 与先前值相等，因此忘记这个 */
			if (!fc_pertrans->inputtypeByVal && !*fc_isNull)
				pfree(DatumGetPointer(*fc_newVal));
		}
		else
		{
			fc_advance_transition_function(fc_aggstate, fc_pertrans, fc_pergroupstate);
			/* 忘记旧值（如果有的话） */
			if (!fc_oldIsNull && !fc_pertrans->inputtypeByVal)
				pfree(DatumGetPointer(fc_oldVal));
			/* 并记住新值以进行后续的相等检查 */
			fc_oldVal = *fc_newVal;
			fc_oldAbbrevVal = fc_newAbbrevVal;
			fc_oldIsNull = *fc_isNull;
			fc_haveOldVal = true;
		}

		MemoryContextSwitchTo(fc_oldContext);
	}

	if (!fc_oldIsNull && !fc_pertrans->inputtypeByVal)
		pfree(DatumGetPointer(fc_oldVal));

	tuplesort_end(fc_pertrans->sortstates[fc_aggstate->current_set]);
	fc_pertrans->sortstates[fc_aggstate->current_set] = NULL;
}

/*
 * 针对多个输入的DISTINCT或ORDER BY聚合
 * 运行转换函数。这个函数在我们完成将所有
 * 输入值输入排序对象后被调用。我们完成
 * 排序，按照排序顺序读取值，并在每个值上运行
 * 转换函数（如果适用则应用DISTINCT）。
 *
 * 此函数仅处理一个分组集（已在
 * aggstate->current_set中设置）。
 *
 * 调用时，CurrentMemoryContext应该是每个查询的上下文。
 */
static void fc_process_ordered_aggregate_multi(AggState *fc_aggstate,
								AggStatePerTrans fc_pertrans,
								AggStatePerGroup fc_pergroupstate)
{
	ExprContext *fc_tmpcontext = fc_aggstate->tmpcontext;
	FunctionCallInfo fcinfo = fc_pertrans->transfn_fcinfo;
	TupleTableSlot *fc_slot1 = fc_pertrans->sortslot;
	TupleTableSlot *fc_slot2 = fc_pertrans->uniqslot;
	int			fc_numTransInputs = fc_pertrans->numTransInputs;
	int			fc_numDistinctCols = fc_pertrans->numDistinctCols;
	Datum		fc_newAbbrevVal = (Datum) 0;
	Datum		fc_oldAbbrevVal = (Datum) 0;
	bool		fc_haveOldValue = false;
	TupleTableSlot *fc_save = fc_aggstate->tmpcontext->ecxt_outertuple;
	int			fc_i;

	tuplesort_performsort(fc_pertrans->sortstates[fc_aggstate->current_set]);

	ExecClearTuple(fc_slot1);
	if (fc_slot2)
		ExecClearTuple(fc_slot2);

	while (tuplesort_gettupleslot(fc_pertrans->sortstates[fc_aggstate->current_set],
								  true, true, fc_slot1, &fc_newAbbrevVal))
	{
		CHECK_FOR_INTERRUPTS();

		fc_tmpcontext->ecxt_outertuple = fc_slot1;
		fc_tmpcontext->ecxt_innertuple = fc_slot2;

		if (fc_numDistinctCols == 0 ||
			!fc_haveOldValue ||
			fc_newAbbrevVal != fc_oldAbbrevVal ||
			!ExecQual(fc_pertrans->equalfnMulti, fc_tmpcontext))
		{
			/*
			 * 提取前numTransInputs列作为数据项传递给
			 * transfn。
			 */
			slot_getsomeattrs(fc_slot1, fc_numTransInputs);

			/* 将值加载到fcinfo中 */
			/* 从1开始，因为第0个参数将是过渡值 */
			for (fc_i = 0; fc_i < fc_numTransInputs; fc_i++)
			{
				fcinfo->args[fc_i + 1].value = fc_slot1->tts_values[fc_i];
				fcinfo->args[fc_i + 1].isnull = fc_slot1->tts_isnull[fc_i];
			}

			fc_advance_transition_function(fc_aggstate, fc_pertrans, fc_pergroupstate);

			if (fc_numDistinctCols > 0)
			{
				/* 交换槽指针以保留当前元组 */
				TupleTableSlot *fc_tmpslot = fc_slot2;

				fc_slot2 = fc_slot1;
				fc_slot1 = fc_tmpslot;
				/* 通过重用简化的键避免ExecQual()调用 */
				fc_oldAbbrevVal = fc_newAbbrevVal;
				fc_haveOldValue = true;
			}
		}

		/* 每次重置上下文 */
		ResetExprContext(fc_tmpcontext);

		ExecClearTuple(fc_slot1);
	}

	if (fc_slot2)
		ExecClearTuple(fc_slot2);

	tuplesort_end(fc_pertrans->sortstates[fc_aggstate->current_set]);
	fc_pertrans->sortstates[fc_aggstate->current_set] = NULL;

	/* 恢复先前的槽，可能在用于分组集合 */
	fc_tmpcontext->ecxt_outertuple = fc_save;
}

/*
 * 计算一个聚合的最终值。
 *
 * 此函数仅处理一个分组集（已经在
 * aggstate->current_set中设置）。
 *
 * finalfn将被运行，结果将在
 * 输出元组上下文中传递；调用者的CurrentMemoryContext无关紧要。
 *
 * finalfn使用状态按transno设置的值。这也可能
 * 被另一个聚合函数使用，因此我们在这里做任何破坏性的事情是重要的。
 */
static void fc_finalize_aggregate(AggState *fc_aggstate,
				   AggStatePerAgg fc_peragg,
				   AggStatePerGroup fc_pergroupstate,
				   Datum *fc_resultVal, bool *fc_resultIsNull)
{
	LOCAL_FCINFO(fcinfo, FUNC_MAX_ARGS);
	bool		fc_anynull = false;
	MemoryContext fc_oldContext;
	int			fc_i;
	ListCell   *fc_lc;
	AggStatePerTrans fc_pertrans = &fc_aggstate->pertrans[fc_peragg->transno];

	fc_oldContext = MemoryContextSwitchTo(fc_aggstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);

	/*
	 * 评估任何直接参数。即使没有finalfn（这不太可能），我们
	 * 也会这样做，以便副作用如预期般发生。
	 * 直接参数放入自1起的位置，位置0
	 * 用于过渡状态值。
	 */
	fc_i = 1;
	foreach(fc_lc, fc_peragg->aggdirectargs)
	{
		ExprState  *fc_expr = (ExprState *) lfirst(fc_lc);

		fcinfo->args[fc_i].value = ExecEvalExpr(fc_expr,
											 fc_aggstate->ss.ps.ps_ExprContext,
											 &fcinfo->args[fc_i].isnull);
		fc_anynull |= fcinfo->args[fc_i].isnull;
		fc_i++;
	}

	/*
	 * 如果提供了agg的finalfn，则应用它，否则返回transValue。
	 */
	if (OidIsValid(fc_peragg->finalfn_oid))
	{
		int			fc_numFinalArgs = fc_peragg->numFinalArgs;

		/* 设置aggstate->curperagg以便进行AggGetAggref() */
		fc_aggstate->curperagg = fc_peragg;

		InitFunctionCallInfoData(*fcinfo, &fc_peragg->finalfn,
								 fc_numFinalArgs,
								 fc_pertrans->aggCollation,
								 (void *) fc_aggstate, NULL);

		/* 填充过渡状态值 */
		fcinfo->args[0].value =
			MakeExpandedObjectReadOnly(fc_pergroupstate->transValue,
									   fc_pergroupstate->transValueIsNull,
									   fc_pertrans->transtypeLen);
		fcinfo->args[0].isnull = fc_pergroupstate->transValueIsNull;
		fc_anynull |= fc_pergroupstate->transValueIsNull;

		/* 用null填充任何剩余的参数位置 */
		for (; fc_i < fc_numFinalArgs; fc_i++)
		{
			fcinfo->args[fc_i].value = (Datum) 0;
			fcinfo->args[fc_i].isnull = true;
			fc_anynull = true;
		}

		if (fcinfo->flinfo->fn_strict && fc_anynull)
		{
			/* 不要用NULL输入调用严格函数 */
			*fc_resultVal = (Datum) 0;
			*fc_resultIsNull = true;
		}
		else
		{
			*fc_resultVal = FunctionCallInvoke(fcinfo);
			*fc_resultIsNull = fcinfo->isnull;
		}
		fc_aggstate->curperagg = NULL;
	}
	else
	{
		/* 不需要MakeExpandedObjectReadOnly；datumCopy将复制它 */
		*fc_resultVal = fc_pergroupstate->transValue;
		*fc_resultIsNull = fc_pergroupstate->transValueIsNull;
	}

	/*
	 * 如果结果是按引用传递的，确保它位于正确的上下文中。
	 */
	if (!fc_peragg->resulttypeByVal && !*fc_resultIsNull &&
		!MemoryContextContains(CurrentMemoryContext,
							   DatumGetPointer(*fc_resultVal)))
		*fc_resultVal = datumCopy(*fc_resultVal,
							   fc_peragg->resulttypeByVal,
							   fc_peragg->resulttypeLen);

	MemoryContextSwitchTo(fc_oldContext);
}

/*
 * 计算一个部分聚合的输出值。
 *
 * 序列化函数将在输出元组上下文中运行，结果也将在此上下文中传递；
 * 调用者的CurrentMemoryContext无关紧要。
 */
static void fc_finalize_partialaggregate(AggState *fc_aggstate,
						  AggStatePerAgg fc_peragg,
						  AggStatePerGroup fc_pergroupstate,
						  Datum *fc_resultVal, bool *fc_resultIsNull)
{
	AggStatePerTrans fc_pertrans = &fc_aggstate->pertrans[fc_peragg->transno];
	MemoryContext fc_oldContext;

	fc_oldContext = MemoryContextSwitchTo(fc_aggstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);

	/*
	 * 如果必须在返回之前序列化transvalue，将设置serialfn_oid
	 */
	if (OidIsValid(fc_pertrans->serialfn_oid))
	{
		/* 不要用NULL输入调用严格的序列化函数。 */
		if (fc_pertrans->serialfn.fn_strict && fc_pergroupstate->transValueIsNull)
		{
			*fc_resultVal = (Datum) 0;
			*fc_resultIsNull = true;
		}
		else
		{
			FunctionCallInfo fcinfo = fc_pertrans->serialfn_fcinfo;

			fcinfo->args[0].value =
				MakeExpandedObjectReadOnly(fc_pergroupstate->transValue,
										   fc_pergroupstate->transValueIsNull,
										   fc_pertrans->transtypeLen);
			fcinfo->args[0].isnull = fc_pergroupstate->transValueIsNull;
			fcinfo->isnull = false;

			*fc_resultVal = FunctionCallInvoke(fcinfo);
			*fc_resultIsNull = fcinfo->isnull;
		}
	}
	else
	{
		/* 不需要MakeExpandedObjectReadOnly；datumCopy将复制它 */
		*fc_resultVal = fc_pergroupstate->transValue;
		*fc_resultIsNull = fc_pergroupstate->transValueIsNull;
	}

	/* 如果结果是按引用传递的，确保它位于正确的上下文中。 */
	if (!fc_peragg->resulttypeByVal && !*fc_resultIsNull &&
		!MemoryContextContains(CurrentMemoryContext,
							   DatumGetPointer(*fc_resultVal)))
		*fc_resultVal = datumCopy(*fc_resultVal,
							   fc_peragg->resulttypeByVal,
							   fc_peragg->resulttypeLen);

	MemoryContextSwitchTo(fc_oldContext);
}

/*
 * 提取构成分组键的属性到hashslot中。这是计算哈希或执行查找所必需的。
 */
static inline void fc_prepare_hash_slot(AggStatePerHash fc_perhash,
				  TupleTableSlot *fc_inputslot,
				  TupleTableSlot *fc_hashslot)
{
	int			fc_i;

	/* 仅将所需的列传输到hashslot中 */
	slot_getsomeattrs(fc_inputslot, fc_perhash->largestGrpColIdx);
	ExecClearTuple(fc_hashslot);

	for (fc_i = 0; fc_i < fc_perhash->numhashGrpCols; fc_i++)
	{
		int			fc_varNumber = fc_perhash->hashGrpColIdxInput[fc_i] - 1;

		fc_hashslot->tts_values[fc_i] = fc_inputslot->tts_values[fc_varNumber];
		fc_hashslot->tts_isnull[fc_i] = fc_inputslot->tts_isnull[fc_varNumber];
	}
	ExecStoreVirtualTuple(fc_hashslot);
}

/*
 * 准备根据指定的代表性元组
 * 槽和分组集来完成和投影。
 *
 * 在指定的元组槽中，强制将所有应在
 * 当前分组集上下文中读取为null的属性设置为null。还将当前组位图存储在
 * GroupingExpr可以访问的位置。
 *
 * 这依赖于三个条件：
 *
 * 1) 永远不会有人试图从这个槽提取整个元组，
 * 只会在评估中引用它，这只会访问单个属性。
 *
 * 2) 不会有系统列需要被设置为null。（如果在
 * 分组子句中引用了一个系统列，它实际上是在外部计划
 * tlist中投影的。）
 *
 * 3) 在给定的阶段内，一旦某个属性被设置为null，
 * 我们永远不需要恢复其值。
 *
 * 以这种方式探查槽有点丑陋，但共识认为替代方案更糟糕。
 */
static void fc_prepare_projection_slot(AggState *fc_aggstate, TupleTableSlot *fc_slot, int fc_currentSet)
{
	if (fc_aggstate->phase->grouped_cols)
	{
		Bitmapset  *fc_grouped_cols = fc_aggstate->phase->grouped_cols[fc_currentSet];

		fc_aggstate->grouped_cols = fc_grouped_cols;

		if (TTS_EMPTY(fc_slot))
		{
			/*
			 * 如果在处理空输入元组时强制所有值为NULL
			 * （即，提供了没有输入行的空分组集）。
			 */
			ExecStoreAllNullTuple(fc_slot);
		}
		else if (fc_aggstate->all_grouped_cols)
		{
			ListCell   *fc_lc;

			/* all_grouped_cols按降序排列 */
			slot_getsomeattrs(fc_slot, linitial_int(fc_aggstate->all_grouped_cols));

			foreach(fc_lc, fc_aggstate->all_grouped_cols)
			{
				int			fc_attnum = lfirst_int(fc_lc);

				if (!bms_is_member(fc_attnum, fc_grouped_cols))
					fc_slot->tts_isnull[fc_attnum - 1] = true;
			}
		}
	}
}

/*
 * 计算一个组的所有聚合的最终值。
 *
 * 此函数一次仅处理一个分组集，调用者必须
 * 已选择该集。调整所提供的pergroup参数以指向
 * 当前集的transvalues也是调用者的责任。
 *
 * 结果存储在输出econtext aggvalues/aggnulls中。
 */
static void fc_finalize_aggregates(AggState *fc_aggstate,
					AggStatePerAgg fc_peraggs,
					AggStatePerGroup fc_pergroup)
{
	ExprContext *fc_econtext = fc_aggstate->ss.ps.ps_ExprContext;
	Datum	   *fc_aggvalues = fc_econtext->ecxt_aggvalues;
	bool	   *fc_aggnulls = fc_econtext->ecxt_aggnulls;
	int			fc_aggno;
	int			fc_transno;

	/*
	 * 如果有任何DISTINCT和/或ORDER BY聚合，则对其
	 * 输入进行排序并运行过渡函数。
	 */
	for (fc_transno = 0; fc_transno < fc_aggstate->numtrans; fc_transno++)
	{
		AggStatePerTrans fc_pertrans = &fc_aggstate->pertrans[fc_transno];
		AggStatePerGroup fc_pergroupstate;

		fc_pergroupstate = &fc_pergroup[fc_transno];

		if (fc_pertrans->numSortCols > 0)
		{
			Assert(fc_aggstate->aggstrategy != AGG_HASHED &&
				   fc_aggstate->aggstrategy != AGG_MIXED);

			if (fc_pertrans->numInputs == 1)
				fc_process_ordered_aggregate_single(fc_aggstate,
												 fc_pertrans,
												 fc_pergroupstate);
			else
				fc_process_ordered_aggregate_multi(fc_aggstate,
												fc_pertrans,
												fc_pergroupstate);
		}
	}

	/*
	 * 运行最终函数。
	 */
	for (fc_aggno = 0; fc_aggno < fc_aggstate->numaggs; fc_aggno++)
	{
		AggStatePerAgg fc_peragg = &fc_peraggs[fc_aggno];
		int			fc_transno = fc_peragg->transno;
		AggStatePerGroup fc_pergroupstate;

		fc_pergroupstate = &fc_pergroup[fc_transno];

		if (DO_AGGSPLIT_SKIPFINAL(fc_aggstate->aggsplit))
			fc_finalize_partialaggregate(fc_aggstate, fc_peragg, fc_pergroupstate,
									  &fc_aggvalues[fc_aggno], &fc_aggnulls[fc_aggno]);
		else
			fc_finalize_aggregate(fc_aggstate, fc_peragg, fc_pergroupstate,
							   &fc_aggvalues[fc_aggno], &fc_aggnulls[fc_aggno]);
	}
}

/*
 * 投影已由finalize_aggregates计算出的组的结果。
 * 返回结果槽，如果没有行被投影（被qual抑制），则返回NULL。
 */
static TupleTableSlot * fc_project_aggregates(AggState *fc_aggstate)
{
	ExprContext *fc_econtext = fc_aggstate->ss.ps.ps_ExprContext;

	/*
	 * 检查qual（HAVING子句）；如果组不匹配，则忽略它。
	 */
	if (ExecQual(fc_aggstate->ss.ps.qual, fc_econtext))
	{
		/*
		 * 使用聚合结果和
		 * 代表输入元组形成并返回投影元组。
		 */
		return ExecProject(fc_aggstate->ss.ps.ps_ProjInfo);
	}
	else
		InstrCountFiltered1(fc_aggstate, 1);

	return NULL;
}

/*
 * 查找所需的输入元组列，将其划分为
 * 聚合和非聚合集合。
 */
static void fc_find_cols(AggState *fc_aggstate, Bitmapset **fc_aggregated, Bitmapset **fc_unaggregated)
{
	Agg		   *fc_agg = (Agg *) fc_aggstate->ss.ps.plan;
	FindColsContext fc_context;

	fc_context.is_aggref = false;
	fc_context.aggregated = NULL;
	fc_context.unaggregated = NULL;

	/* 检查 tlist 和 quals */
	(void) fc_find_cols_walker((Node *) fc_agg->plan.targetlist, &fc_context);
	(void) fc_find_cols_walker((Node *) fc_agg->plan.qual, &fc_context);

	/* 在某些情况下，分组列不会出现在 tlist 中 */
	for (int fc_i = 0; fc_i < fc_agg->numCols; fc_i++)
		fc_context.unaggregated = bms_add_member(fc_context.unaggregated,
											  fc_agg->grpColIdx[fc_i]);

	*fc_aggregated = fc_context.aggregated;
	*fc_unaggregated = fc_context.unaggregated;
}

static bool fc_find_cols_walker(Node *fc_node, FindColsContext *fc_context)
{
	if (fc_node == NULL)
		return false;
	if (IsA(fc_node, Var))
	{
		Var		   *fc_var = (Var *) fc_node;

		/* setrefs.c 应该已将 varno 设置为 OUTER_VAR */
		Assert(fc_var->varno == OUTER_VAR);
		Assert(fc_var->varlevelsup == 0);
		if (fc_context->is_aggref)
			fc_context->aggregated = bms_add_member(fc_context->aggregated,
												 fc_var->varattno);
		else
			fc_context->unaggregated = bms_add_member(fc_context->unaggregated,
												   fc_var->varattno);
		return false;
	}
	if (IsA(fc_node, Aggref))
	{
		Assert(!fc_context->is_aggref);
		fc_context->is_aggref = true;
		expression_tree_walker(fc_node, fc_find_cols_walker, (void *) fc_context);
		fc_context->is_aggref = false;
		return false;
	}
	return expression_tree_walker(fc_node, fc_find_cols_walker,
								  (void *) fc_context);
}

/*
 * （重）初始化哈希表为空。
 *
 * 为了实现哈希聚合，我们需要一个哈希表来存储一个
 * 代表元组和一个 AggStatePerGroup 结构数组，用于每个
 * 不同的 GROUP BY 列值集合。我们根据
 * GROUP BY 列计算哈希键。每组数据在 lookup_hash_entry()
 * 中分配，每个条目都有。
 *
 * 我们为每个分组集创建一个单独的哈希表和相关的 perhash 数据结构，
 * 以进行哈希处理。
 *
 * 哈希表的内容始终存在于哈希上下文的每个元组
 * 内存上下文中（所有表共享一个，因为它们都会在同一时间重置）。
 */
static void fc_build_hash_tables(AggState *fc_aggstate)
{
	int			fc_setno;

	for (fc_setno = 0; fc_setno < fc_aggstate->num_hashes; ++fc_setno)
	{
		AggStatePerHash fc_perhash = &fc_aggstate->perhash[fc_setno];
		long		fc_nbuckets;
		Size		fc_memory;

		if (fc_perhash->hashtable != NULL)
		{
			ResetTupleHashTable(fc_perhash->hashtable);
			continue;
		}

		Assert(fc_perhash->aggnode->numGroups > 0);

		fc_memory = fc_aggstate->hash_mem_limit / fc_aggstate->num_hashes;

		/* 选择每个哈希表合理数量的桶 */
		fc_nbuckets = fc_hash_choose_num_buckets(fc_aggstate->hashentrysize,
										   fc_perhash->aggnode->numGroups,
										   fc_memory);

		fc_build_hash_table(fc_aggstate, fc_setno, fc_nbuckets);
	}

	fc_aggstate->hash_ngroups_current = 0;
}

/*
 * 为这个分组集构建一个单一的哈希表。
 */
static void fc_build_hash_table(AggState *fc_aggstate, int fc_setno, long fc_nbuckets)
{
	AggStatePerHash fc_perhash = &fc_aggstate->perhash[fc_setno];
	MemoryContext fc_metacxt = fc_aggstate->hash_metacxt;
	MemoryContext fc_hashcxt = fc_aggstate->hashcontext->ecxt_per_tuple_memory;
	MemoryContext fc_tmpcxt = fc_aggstate->tmpcontext->ecxt_per_tuple_memory;
	Size		fc_additionalsize;

	Assert(fc_aggstate->aggstrategy == AGG_HASHED ||
		   fc_aggstate->aggstrategy == AGG_MIXED);

	/*
	 * 用于确保初始哈希表分配不超过
	 * hash_mem。请注意，估算不包括
	 * 通过引用传递的过渡数据值的空间，也不包括
	 * 每个组的代表元组。
	 */
	fc_additionalsize = fc_aggstate->numtrans * sizeof(AggStatePerGroupData);

	fc_perhash->hashtable = BuildTupleHashTableExt(&fc_aggstate->ss.ps,
												fc_perhash->hashslot->tts_tupleDescriptor,
												fc_perhash->numCols,
												fc_perhash->hashGrpColIdxHash,
												fc_perhash->eqfuncoids,
												fc_perhash->hashfunctions,
												fc_perhash->aggnode->grpCollations,
												fc_nbuckets,
												fc_additionalsize,
												fc_metacxt,
												fc_hashcxt,
												fc_tmpcxt,
												DO_AGGSPLIT_SKIPFINAL(fc_aggstate->aggsplit));
}

/*
 * 计算实际需要存储在哈希表条目中的列。来自子计划节点的
 * 输入元组将包含分组列，
 * 在我们的目标列表和 qual 中引用的其他列，用于
 * 计算聚合函数的列，以及可能根本不使用的垃圾列。
 * 只有前两种类型的列需要存储在
 * 哈希表中，去掉其他列可以使表条目
 * 明显更小。哈希表只包含相关的列，
 * 并在 lookup_hash_entry() / agg_retrieve_hash_table()
 * 中以普通输入描述符的格式进行压缩/解压缩。
 *
 * 除了分组列之外的附加列来自两个
 * 源：首先是我们不需要单独分组的功能依赖列，
 * 其次是用于行标记的 ctids。
 *
 * 为了消除重复项，我们构建所需列的位图集，然后
 * 构建包含在哈希表中的列数组。如果传入的 grpColIdx
 * 有重复项，可能仍然会有重复项，这在 semijoins/distinct 的边缘情况下发生；
 * 这些不能始终被删除，因为不确定重复列是否使用相同的
 * 哈希函数。
 *
 * 请注意，该数组在 ExecReScanAgg 期间保留，因此我们在
 * 每个查询上下文中分配它（与哈希表本身不同）。
 */
static void fc_find_hash_columns(AggState *fc_aggstate)
{
	Bitmapset  *fc_base_colnos;
	Bitmapset  *fc_aggregated_colnos;
	TupleDesc	fc_scanDesc = fc_aggstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
	List	   *fc_outerTlist = outerPlanState(fc_aggstate)->plan->targetlist;
	int			fc_numHashes = fc_aggstate->num_hashes;
	EState	   *fc_estate = fc_aggstate->ss.ps.state;
	int			fc_j;

	/* 查找将在 tlist 和 qual 中需要的 Vars */
	fc_find_cols(fc_aggstate, &fc_aggregated_colnos, &fc_base_colnos);
	fc_aggstate->colnos_needed = bms_union(fc_base_colnos, fc_aggregated_colnos);
	fc_aggstate->max_colno_needed = 0;
	fc_aggstate->all_cols_needed = true;

	for (int fc_i = 0; fc_i < fc_scanDesc->natts; fc_i++)
	{
		int			fc_colno = fc_i + 1;

		if (bms_is_member(fc_colno, fc_aggstate->colnos_needed))
			fc_aggstate->max_colno_needed = fc_colno;
		else
			fc_aggstate->all_cols_needed = false;
	}

	for (fc_j = 0; fc_j < fc_numHashes; ++fc_j)
	{
		AggStatePerHash fc_perhash = &fc_aggstate->perhash[fc_j];
		Bitmapset  *fc_colnos = bms_copy(fc_base_colnos);
		AttrNumber *fc_grpColIdx = fc_perhash->aggnode->grpColIdx;
		List	   *fc_hashTlist = NIL;
		TupleDesc	fc_hashDesc;
		int			fc_maxCols;
		int			fc_i;

		fc_perhash->largestGrpColIdx = 0;

		/*
		 * 如果我们正在进行分组集，则某些 Vars 可能在
		 * tlist/qual 中被引用，以便其他分组集受益，
		 * 但在哈希时不需要；即 prepare_projection_slot 将将它们设为 null，
		 * 因此没有必要存储它们。使用 prepare_projection_slot 的
		 * 逻辑来确定哪些。
		 */
		if (fc_aggstate->phases[0].grouped_cols)
		{
			Bitmapset  *fc_grouped_cols = fc_aggstate->phases[0].grouped_cols[fc_j];
			ListCell   *fc_lc;

			foreach(fc_lc, fc_aggstate->all_grouped_cols)
			{
				int			fc_attnum = lfirst_int(fc_lc);

				if (!bms_is_member(fc_attnum, fc_grouped_cols))
					fc_colnos = bms_del_member(fc_colnos, fc_attnum);
			}
		}

		/*
		 * 计算输入列的最大数量，考虑到
		 * grpColIdx 数组中的可能重复，这在某些边缘
		 * 情况下可能发生，即 HashAggregate 作为 semijoin 或
		 * DISTINCT 的一部分生成。
		 */
		fc_maxCols = bms_num_members(fc_colnos) + fc_perhash->numCols;

		fc_perhash->hashGrpColIdxInput =
			palloc(fc_maxCols * sizeof(AttrNumber));
		fc_perhash->hashGrpColIdxHash =
			palloc(fc_perhash->numCols * sizeof(AttrNumber));

		/* 将所有分组列添加到 colnos */
		for (fc_i = 0; fc_i < fc_perhash->numCols; fc_i++)
			fc_colnos = bms_add_member(fc_colnos, fc_grpColIdx[fc_i]);

		/*
		 * 首先为直接哈希的列构建映射。这是
		 * 首先，因为在计算哈希值和
		 * 比较元组以进行精确匹配时将访问它们。我们还
		 * 为 execGrouping 构建简单映射，以便它知道在哪里
		 * 查找待哈希 / 比较的输入列。
		 */
		for (fc_i = 0; fc_i < fc_perhash->numCols; fc_i++)
		{
			fc_perhash->hashGrpColIdxInput[fc_i] = fc_grpColIdx[fc_i];
			fc_perhash->hashGrpColIdxHash[fc_i] = fc_i + 1;
			fc_perhash->numhashGrpCols++;
			/* 删除已映射的列 */
			bms_del_member(fc_colnos, fc_grpColIdx[fc_i]);
		}

		/* 并添加剩余列 */
		while ((fc_i = bms_first_member(fc_colnos)) >= 0)
		{
			fc_perhash->hashGrpColIdxInput[fc_perhash->numhashGrpCols] = fc_i;
			fc_perhash->numhashGrpCols++;
		}

		/* 并为哈希表构建一个元组描述符 */
		for (fc_i = 0; fc_i < fc_perhash->numhashGrpCols; fc_i++)
		{
			int			fc_varNumber = fc_perhash->hashGrpColIdxInput[fc_i] - 1;

			fc_hashTlist = lappend(fc_hashTlist, list_nth(fc_outerTlist, fc_varNumber));
			fc_perhash->largestGrpColIdx =
				Max(fc_varNumber + 1, fc_perhash->largestGrpColIdx);
		}

		fc_hashDesc = ExecTypeFromTL(fc_hashTlist);

		execTuplesHashPrepare(fc_perhash->numCols,
							  fc_perhash->aggnode->grpOperators,
							  &fc_perhash->eqfuncoids,
							  &fc_perhash->hashfunctions);
		fc_perhash->hashslot =
			ExecAllocTableSlot(&fc_estate->es_tupleTable, fc_hashDesc,
							   &TTSOpsMinimalTuple);

		list_free(fc_hashTlist);
		bms_free(fc_colnos);
	}

	bms_free(fc_base_colnos);
}

/*
 * 估算每个哈希表条目的开销。
 */
Size hash_agg_entry_size(int fc_numTrans, Size fc_tupleWidth, Size fc_transitionSpace)
{
	Size		fc_tupleChunkSize;
	Size		fc_pergroupChunkSize;
	Size		fc_transitionChunkSize;
	Size		fc_tupleSize = (MAXALIGN(SizeofMinimalTupleHeader) +
							 fc_tupleWidth);
	Size		fc_pergroupSize = fc_numTrans * sizeof(AggStatePerGroupData);

	fc_tupleChunkSize = CHUNKHDRSZ + fc_tupleSize;

	if (fc_pergroupSize > 0)
		fc_pergroupChunkSize = CHUNKHDRSZ + fc_pergroupSize;
	else
		fc_pergroupChunkSize = 0;

	if (fc_transitionSpace > 0)
		fc_transitionChunkSize = CHUNKHDRSZ + fc_transitionSpace;
	else
		fc_transitionChunkSize = 0;

	return
		sizeof(TupleHashEntryData) +
		fc_tupleChunkSize +
		fc_pergroupChunkSize +
		fc_transitionChunkSize;
}


/*
 * hashagg_recompile_expressions()
 *
 * 确定正确的阶段，根据参数编译正确的表达式，然后将 phase->evalfunc 设置为该表达式。
 *
 * 根据 hash 聚合是否溢出以及是从外部计划读取还是从磁带读取，需要不同版本的编译表达式。在溢出到磁盘之前，表达式从外部计划读取，并且不需要执行 NULL 检查。在 HashAgg 开始溢出后，哈希表中将不会创建新组，AggStatePerGroup 数组可能为 NULL；因此，我们需要在表达式中添加一个空指针检查。然后，在从磁带读取溢出数据时，我们将外部槽类型更改为固定的最小元组槽。
 *
 * 每次重新编译会浪费资源，因此在 AggStatePerPhase 中缓存编译表达式，并在适当时重用。
 */ 
static void fc_hashagg_recompile_expressions(AggState *fc_aggstate, bool fc_minslot, bool fc_nullcheck)
{
	AggStatePerPhase fc_phase;
	int			fc_i = fc_minslot ? 1 : 0;
	int			fc_j = fc_nullcheck ? 1 : 0;

	Assert(fc_aggstate->aggstrategy == AGG_HASHED ||
		   fc_aggstate->aggstrategy == AGG_MIXED);

	if (fc_aggstate->aggstrategy == AGG_HASHED)
		fc_phase = &fc_aggstate->phases[0];
	else						/* AGG_MIXED */ 
		fc_phase = &fc_aggstate->phases[1];

	if (fc_phase->evaltrans_cache[fc_i][fc_j] == NULL)
	{
		const TupleTableSlotOps *fc_outerops = fc_aggstate->ss.ps.outerops;
		bool		fc_outerfixed = fc_aggstate->ss.ps.outeropsfixed;
		bool		fc_dohash = true;
		bool		fc_dosort = false;

		/*
		 * 如果 minslot 为真，这意味着我们正在处理溢出的批次
		 * （在 agg_refill_hash_table() 内），我们必须避免推进
		 * 排序的分组集。
		 */ 
		if (fc_aggstate->aggstrategy == AGG_MIXED && !fc_minslot)
			fc_dosort = true;

		/* 在编译表达式时暂时更改 outerops */ 
		if (fc_minslot)
		{
			fc_aggstate->ss.ps.outerops = &TTSOpsMinimalTuple;
			fc_aggstate->ss.ps.outeropsfixed = true;
		}

		fc_phase->evaltrans_cache[fc_i][fc_j] = ExecBuildAggTrans(fc_aggstate, fc_phase,
														 fc_dosort, fc_dohash,
														 fc_nullcheck);

		/* 恢复 */ 
		fc_aggstate->ss.ps.outerops = fc_outerops;
		fc_aggstate->ss.ps.outeropsfixed = fc_outerfixed;
	}

	fc_phase->evaltrans = fc_phase->evaltrans_cache[fc_i][fc_j];
}

/*
 * 设置触发溢出的限制，以避免超过 hash_mem。考虑
 * 我们期望创建的分区数量（如果确实会溢出）。
 *
 * 有两个限制：一个是内存限制，还有一个是 ngroups 限制。当我们期望过渡值比初始值大得多时，ngroups 限制变得重要。
 */ 
void hash_agg_set_limits(double fc_hashentrysize, double fc_input_groups, int fc_used_bits,
					Size *fc_mem_limit, uint64 *fc_ngroups_limit,
					int *fc_num_partitions)
{
	int			fc_npartitions;
	Size		fc_partition_mem;
	Size		fc_hash_mem_limit = get_hash_memory_limit();

	/* 如果不预期溢出，则使用所有 hash_mem */ 
	if (fc_input_groups * fc_hashentrysize <= fc_hash_mem_limit)
	{
		if (fc_num_partitions != NULL)
			*fc_num_partitions = 0;
		*fc_mem_limit = fc_hash_mem_limit;
		*fc_ngroups_limit = fc_hash_mem_limit / fc_hashentrysize;
		return;
	}

	/*
	 * 计算溢出的预期内存需求，即同时需要打开的
	 * 所有磁带所需的缓冲区的大小。
	 * 然后，将其从用于保持哈希表的可用内存中减去。
	 */ 
	fc_npartitions = fc_hash_choose_num_partitions(fc_input_groups,
											 fc_hashentrysize,
											 fc_used_bits,
											 NULL);
	if (fc_num_partitions != NULL)
		*fc_num_partitions = fc_npartitions;

	fc_partition_mem =
		HASHAGG_READ_BUFFER_SIZE +
		HASHAGG_WRITE_BUFFER_SIZE * fc_npartitions;

	/*
	 * 不要将限制设置低于 hash_mem 的 3/4。在这种情况下，我们处于
	 * 最小分区数量，因此无论如何不会显著超出工作内存。
	 */ 
	if (fc_hash_mem_limit > 4 * fc_partition_mem)
		*fc_mem_limit = fc_hash_mem_limit - fc_partition_mem;
	else
		*fc_mem_limit = fc_hash_mem_limit * 0.75;

	if (*fc_mem_limit > fc_hashentrysize)
		*fc_ngroups_limit = *fc_mem_limit / fc_hashentrysize;
	else
		*fc_ngroups_limit = 1;
}

/*
 * hash_agg_check_limits
 *
 * 在向哈希表添加新组后，检查是否需要进入
 * 溢出模式。分配可能在不添加新组的情况下发生（例如，
 * 如果过渡状态大小增长），因此此检查并不完美。
 */ 
static void fc_hash_agg_check_limits(AggState *fc_aggstate)
{
	uint64		fc_ngroups = fc_aggstate->hash_ngroups_current;
	Size		fc_meta_mem = MemoryContextMemAllocated(fc_aggstate->hash_metacxt,
													 true);
	Size		fc_hashkey_mem = MemoryContextMemAllocated(fc_aggstate->hashcontext->ecxt_per_tuple_memory,
														true);

	/*
	 * 除非哈希表中至少有一个组，否则不要溢出，以便我们
	 * 可以确保即使在边缘情况下也能取得进展。
	 */ 
	if (fc_aggstate->hash_ngroups_current > 0 &&
		(fc_meta_mem + fc_hashkey_mem > fc_aggstate->hash_mem_limit ||
		 fc_ngroups > fc_aggstate->hash_ngroups_limit))
	{
		fc_hash_agg_enter_spill_mode(fc_aggstate);
	}
}

/*
 * 进入“溢出模式”，意味着不向任何哈希
 * 表中添加新组。将会创建新组的元组则会被溢出，并
 * 在稍后处理。
 */ 
static void fc_hash_agg_enter_spill_mode(AggState *fc_aggstate)
{
	fc_aggstate->hash_spill_mode = true;
	fc_hashagg_recompile_expressions(fc_aggstate, fc_aggstate->table_filled, true);

	if (!fc_aggstate->hash_ever_spilled)
	{
		Assert(fc_aggstate->hash_tapeset == NULL);
		Assert(fc_aggstate->hash_spills == NULL);

		fc_aggstate->hash_ever_spilled = true;

		fc_aggstate->hash_tapeset = LogicalTapeSetCreate(true, NULL, -1);

		fc_aggstate->hash_spills = palloc(sizeof(HashAggSpill) * fc_aggstate->num_hashes);

		for (int fc_setno = 0; fc_setno < fc_aggstate->num_hashes; fc_setno++)
		{
			AggStatePerHash fc_perhash = &fc_aggstate->perhash[fc_setno];
			HashAggSpill *fc_spill = &fc_aggstate->hash_spills[fc_setno];

			fc_hashagg_spill_init(fc_spill, fc_aggstate->hash_tapeset, 0,
							   fc_perhash->aggnode->numGroups,
							   fc_aggstate->hashentrysize);
		}
	}
}

/*
 * 填充哈希表后更新指标。
 *
 * 如果从外部计划读取，from_tape 应为 false；如果从另一个磁带读取，from_tape 应为 true。
 */
static void fc_hash_agg_update_metrics(AggState *fc_aggstate, bool fc_from_tape, int fc_npartitions)
{
	Size		fc_meta_mem;
	Size		fc_hashkey_mem;
	Size		fc_buffer_mem;
	Size		fc_total_mem;

	if (fc_aggstate->aggstrategy != AGG_MIXED &&
		fc_aggstate->aggstrategy != AGG_HASHED)
		return;

	/* 哈希表本身的内存 */
	fc_meta_mem = MemoryContextMemAllocated(fc_aggstate->hash_metacxt, true);

	/* 组键和过渡状态的内存 */
	fc_hashkey_mem = MemoryContextMemAllocated(fc_aggstate->hashcontext->ecxt_per_tuple_memory, true);

	/* 如果溢出，读/写磁带缓冲区的内存 */
	fc_buffer_mem = fc_npartitions * HASHAGG_WRITE_BUFFER_SIZE;
	if (fc_from_tape)
		fc_buffer_mem += HASHAGG_READ_BUFFER_SIZE;

	/* 更新峰值内存 */
	fc_total_mem = fc_meta_mem + fc_hashkey_mem + fc_buffer_mem;
	if (fc_total_mem > fc_aggstate->hash_mem_peak)
		fc_aggstate->hash_mem_peak = fc_total_mem;

	/* 更新磁盘使用情况 */
	if (fc_aggstate->hash_tapeset != NULL)
	{
		uint64		fc_disk_used = LogicalTapeSetBlocks(fc_aggstate->hash_tapeset) * (BLCKSZ / 1024);

		if (fc_aggstate->hash_disk_used < fc_disk_used)
			fc_aggstate->hash_disk_used = fc_disk_used;
	}

	/* 基于内容更新 hashentrysize 估计 */
	if (fc_aggstate->hash_ngroups_current > 0)
	{
		fc_aggstate->hashentrysize =
			sizeof(TupleHashEntryData) +
			(fc_hashkey_mem / (double) fc_aggstate->hash_ngroups_current);
	}
}

/*
 * 为初始哈希表大小选择一个合理的桶数。
 */
static long fc_hash_choose_num_buckets(double fc_hashentrysize, long fc_ngroups, Size fc_memory)
{
	long		fc_max_nbuckets;
	long		fc_nbuckets = fc_ngroups;

	fc_max_nbuckets = fc_memory / fc_hashentrysize;

	/*
	 * 低估优于高估。桶太多会挤占组键和过渡状态值的空间。
	 */
	fc_max_nbuckets >>= 1;

	if (fc_nbuckets > fc_max_nbuckets)
		fc_nbuckets = fc_max_nbuckets;

	return Max(fc_nbuckets, 1);
}

/*
 * 确定溢出时创建的分区数量，这将始终是 2 的幂。如果 log2_npartitions 不为 NULL，则设置
 * *log2_npartitions 为分区数的 log2()。
 */
static int fc_hash_choose_num_partitions(double fc_input_groups, double fc_hashentrysize,
						   int fc_used_bits, int *fc_log2_npartitions)
{
	Size		fc_hash_mem_limit = get_hash_memory_limit();
	double		fc_partition_limit;
	double		fc_mem_wanted;
	double		fc_dpartitions;
	int			fc_npartitions;
	int			fc_partition_bits;

	/*
	 * 避免创建过多分区，以至于打开的分区文件的内存需求超过 hash_mem 的 1/4。
	 */
	fc_partition_limit =
		(fc_hash_mem_limit * 0.25 - HASHAGG_READ_BUFFER_SIZE) /
		HASHAGG_WRITE_BUFFER_SIZE;

	fc_mem_wanted = HASHAGG_PARTITION_FACTOR * fc_input_groups * fc_hashentrysize;

	/* 创建足够的分区，以便每个分区都可能适合内存 */
	fc_dpartitions = 1 + (fc_mem_wanted / fc_hash_mem_limit);

	if (fc_dpartitions > fc_partition_limit)
		fc_dpartitions = fc_partition_limit;

	if (fc_dpartitions < HASHAGG_MIN_PARTITIONS)
		fc_dpartitions = HASHAGG_MIN_PARTITIONS;
	if (fc_dpartitions > HASHAGG_MAX_PARTITIONS)
		fc_dpartitions = HASHAGG_MAX_PARTITIONS;

	/* HASHAGG_MAX_PARTITIONS 限制使之安全 */
	fc_npartitions = (int) fc_dpartitions;

	/* ceil(log2(npartitions)) */
	fc_partition_bits = my_log2(fc_npartitions);

	/* 确保我们不会耗尽哈希位 */
	if (fc_partition_bits + fc_used_bits >= 32)
		fc_partition_bits = 32 - fc_used_bits;

	if (fc_log2_npartitions != NULL)
		*fc_log2_npartitions = fc_partition_bits;

	/* 分区数量将是 2 的幂 */
	fc_npartitions = 1 << fc_partition_bits;

	return fc_npartitions;
}

/*
 * 初始化新创建的 TupleHashEntry。
 */
static void fc_initialize_hash_entry(AggState *fc_aggstate, TupleHashTable hashtable,
					  TupleHashEntry fc_entry)
{
	AggStatePerGroup fc_pergroup;
	int			fc_transno;

	fc_aggstate->hash_ngroups_current++;
	fc_hash_agg_check_limits(fc_aggstate);

	/* 无需为每个组分配或初始化状态 */
	if (fc_aggstate->numtrans == 0)
		return;

	fc_pergroup = (AggStatePerGroup)
		MemoryContextAlloc(hashtable->tablecxt,
						   sizeof(AggStatePerGroupData) * fc_aggstate->numtrans);

	fc_entry->additional = fc_pergroup;

	/*
	 * 为新元组组初始化聚合，lookup_hash_entries()
	 * 已经选择了相关的分组集。
	 */
	for (fc_transno = 0; fc_transno < fc_aggstate->numtrans; fc_transno++)
	{
		AggStatePerTrans fc_pertrans = &fc_aggstate->pertrans[fc_transno];
		AggStatePerGroup fc_pergroupstate = &fc_pergroup[fc_transno];

		fc_initialize_aggregate(fc_aggstate, fc_pertrans, fc_pergroupstate);
	}
}

/*
 * 在所有哈希分组集中查找当前元组的哈希条目。
 *
 * 注意，lookup_hash_entry 可能会重置 tmpcontext。
 *
 * 如果我们处于“溢出模式”，则某些条目可能会保持 NULL。相同的元组
 * 将属于每个分组集的不同组，因此可能与一个集合中内存中的组匹配，而与另一个集合中不在内存中的组匹配。当处于“溢出模式”时，
 * 元组将在每个其不匹配内存中组的分组集中被溢出。
 *
 * 注：可能会将相同的元组溢出多个不同的分组集。这看起来可能是浪费，但实际上是一种权衡：如果我们在多个分组集上多次溢出该元组，
 * 则可以为每个分组集进行分区，从而使哈希表的重新填充变得非常高效。
 */
static void fc_lookup_hash_entries(AggState *fc_aggstate)
{
	AggStatePerGroup *fc_pergroup = fc_aggstate->hash_pergroup;
	TupleTableSlot *fc_outerslot = fc_aggstate->tmpcontext->ecxt_outertuple;
	int			fc_setno;

	for (fc_setno = 0; fc_setno < fc_aggstate->num_hashes; fc_setno++)
	{
		AggStatePerHash fc_perhash = &fc_aggstate->perhash[fc_setno];
		TupleHashTable hashtable = fc_perhash->hashtable;
		TupleTableSlot *fc_hashslot = fc_perhash->hashslot;
		TupleHashEntry fc_entry;
		uint32		fc_hash;
		bool		fc_isnew = false;
		bool	   *fc_p_isnew;

		/* 如果哈希表已溢出，则不要创建新条目 */
		fc_p_isnew = fc_aggstate->hash_spill_mode ? NULL : &fc_isnew;

		fc_select_current_set(fc_aggstate, fc_setno, true);
		fc_prepare_hash_slot(fc_perhash,
						  fc_outerslot,
						  fc_hashslot);

		fc_entry = LookupTupleHashEntry(hashtable, fc_hashslot,
									 fc_p_isnew, &fc_hash);

		if (fc_entry != NULL)
		{
			if (fc_isnew)
				fc_initialize_hash_entry(fc_aggstate, hashtable, fc_entry);
			fc_pergroup[fc_setno] = fc_entry->additional;
		}
		else
		{
			HashAggSpill *fc_spill = &fc_aggstate->hash_spills[fc_setno];
			TupleTableSlot *fc_slot = fc_aggstate->tmpcontext->ecxt_outertuple;

			if (fc_spill->partitions == NULL)
				fc_hashagg_spill_init(fc_spill, fc_aggstate->hash_tapeset, 0,
								   fc_perhash->aggnode->numGroups,
								   fc_aggstate->hashentrysize);

			fc_hashagg_spill_tuple(fc_aggstate, fc_spill, fc_slot, fc_hash);
			fc_pergroup[fc_setno] = NULL;
		}
	}
}

/*
 * ExecAgg -
 *
 *	  ExecAgg 从其外部子计划接收元组，并对
 *	  目标列表或节点的 qual 中出现的每个聚合函数使用的适当属性进行聚合（Aggref
 *	  节点）。聚合的元组数量取决于选择了分组聚合还是普通聚合。在分组聚合中，
 *	  我们为每个组生成一个结果行；在普通聚合中，整个查询的数据只有一行结果。
 *	  在这两种情况下，每个聚合的值都存储在表达式上下文中，以便在 ExecProject 评估
 *	  结果元组时使用。
 */
static TupleTableSlot * fc_ExecAgg(PlanState *fc_pstate)
{
	AggState   *fc_node = castNode(AggState, fc_pstate);
	TupleTableSlot *fc_result = NULL;

	CHECK_FOR_INTERRUPTS();

	if (!fc_node->agg_done)
	{
		/* 基于策略进行调度 */
		switch (fc_node->phase->aggstrategy)
		{
			case AGG_HASHED:
				if (!fc_node->table_filled)
					fc_agg_fill_hash_table(fc_node);
				/* FALLTHROUGH */
			case AGG_MIXED:
				fc_result = fc_agg_retrieve_hash_table(fc_node);
				break;
			case AGG_PLAIN:
			case AGG_SORTED:
				fc_result = fc_agg_retrieve_direct(fc_node);
				break;
		}

		if (!TupIsNull(fc_result))
			return fc_result;
	}

	return NULL;
}

/*
 * ExecAgg 非哈希情况
 */
static TupleTableSlot * fc_agg_retrieve_direct(AggState *fc_aggstate)
{
	Agg		   *fc_node = fc_aggstate->phase->aggnode;
	ExprContext *fc_econtext;
	ExprContext *fc_tmpcontext;
	AggStatePerAgg fc_peragg;
	AggStatePerGroup *fc_pergroups;
	TupleTableSlot *fc_outerslot;
	TupleTableSlot *fc_firstSlot;
	TupleTableSlot *fc_result;
	bool		fc_hasGroupingSets = fc_aggstate->phase->numsets > 0;
	int			fc_numGroupingSets = Max(fc_aggstate->phase->numsets, 1);
	int			fc_currentSet;
	int			fc_nextSetSize;
	int			fc_numReset;
	int			fc_i;

	/*
	 * 从节点获取状态信息
	 *
	 * econtext 是每个输出元组的表达式上下文
	 *
	 * tmpcontext 是每个输入元组的表达式上下文
	 */
	fc_econtext = fc_aggstate->ss.ps.ps_ExprContext;
	fc_tmpcontext = fc_aggstate->tmpcontext;

	fc_peragg = fc_aggstate->peragg;
	fc_pergroups = fc_aggstate->pergroups;
	fc_firstSlot = fc_aggstate->ss.ss_ScanTupleSlot;

	/*
	 * 我们循环检索组，直到找到一个匹配
	 * aggstate->ss.ps.qual
	 *
	 * 对于分组集，我们有一个不变量，即 aggstate->projected_set
	 * 要么是 -1（初始调用），要么是我们刚刚完成的组在 gset_lengths 中的索引（从 0 开始）。
	 */
	while (!fc_aggstate->agg_done)
	{
		/*
		 * 为每个组清除每个输出元组上下文，以及
		 * aggcontext（包含旧组的任何引用传递 transvalues）。某些聚合函数在子
		 * 上下文中存储工作状态；这些现在会自动重置，而我们无需做特别的事情。
		 *
		 * 我们使用 ReScanExprContext 而不是 ResetExprContext，因为我们希望
		 * 任何注册的关闭回调被调用。这允许聚合函数确保它们清理了任何非内存
		 * 资源。
		 */
		ReScanExprContext(fc_econtext);

		/*
		 * 确定在此边界需要重置多少个分组集。
		 */
		if (fc_aggstate->projected_set >= 0 &&
			fc_aggstate->projected_set < fc_numGroupingSets)
			fc_numReset = fc_aggstate->projected_set + 1;
		else
			fc_numReset = fc_numGroupingSets;

		/*
		 * numReset 可以在相位边界变化，但没关系；我们想要重置在 _这_ 相位中使用的上下文，
		 * 待会儿，在可能改变相位后，初始化正确数量的聚合以适应 _新_ 相位。
		 */

		for (fc_i = 0; fc_i < fc_numReset; fc_i++)
		{
			ReScanExprContext(fc_aggstate->aggcontexts[fc_i]);
		}

		/*
		 * 检查输入是否完整，以及在此相位中没有更多组可以投影；
		 * 转到下一个相位或标记为完成。
		 */
		if (fc_aggstate->input_done == true &&
			fc_aggstate->projected_set >= (fc_numGroupingSets - 1))
		{
			if (fc_aggstate->current_phase < fc_aggstate->numphases - 1)
			{
				fc_initialize_phase(fc_aggstate, fc_aggstate->current_phase + 1);
				fc_aggstate->input_done = false;
				fc_aggstate->projected_set = -1;
				fc_numGroupingSets = Max(fc_aggstate->phase->numsets, 1);
				fc_node = fc_aggstate->phase->aggnode;
				fc_numReset = fc_numGroupingSets;
			}
			else if (fc_aggstate->aggstrategy == AGG_MIXED)
			{
				/*
				 * 混合模式；我们已经输出了所有的分组内容，并且有
				 * 完整的哈希表，因此切换到输出这些。
				 */
				fc_initialize_phase(fc_aggstate, 0);
				fc_aggstate->table_filled = true;
				ResetTupleHashIterator(fc_aggstate->perhash[0].hashtable,
									   &fc_aggstate->perhash[0].hashiter);
				fc_select_current_set(fc_aggstate, 0, true);
				return fc_agg_retrieve_hash_table(fc_aggstate);
			}
			else
			{
				fc_aggstate->agg_done = true;
				break;
			}
		}

		/*
		 * 获取上一个投影后下一个分组集中的列数（如果有的话）。
		 * 这是比较的列数，以查看我们是否也达到了该集合的边界。
		 */
		if (fc_aggstate->projected_set >= 0 &&
			fc_aggstate->projected_set < (fc_numGroupingSets - 1))
			fc_nextSetSize = fc_aggstate->phase->gset_lengths[fc_aggstate->projected_set + 1];
		else
			fc_nextSetSize = 0;

		/*----------
		 * 如果当前分组集中存在子组，则进行投影。
		 *
		 * 如果我们有一个新组：
		 *	- 我们的输入已用完，但尚未投影所有分组集
		 *	  （上述已检查）
		 * 或者
		 *	  - 我们已经投影了一行，该行不属于上一个分组
		 *		集合
		 *	  并且
		 *	  - 下一个分组集至少有一个分组列（因为
		 *		空分组集仅在输入用尽后才会投影）
		 *	  并且
		 *	  - 之前和待处理的行在下一个分组集合的
		 *		分组列上不同
		 *----------
		 */
		fc_tmpcontext->ecxt_innertuple = fc_econtext->ecxt_outertuple;
		if (fc_aggstate->input_done ||
			(fc_node->aggstrategy != AGG_PLAIN &&
			 fc_aggstate->projected_set != -1 &&
			 fc_aggstate->projected_set < (fc_numGroupingSets - 1) &&
			 fc_nextSetSize > 0 &&
			 !ExecQualAndReset(fc_aggstate->phase->eqfunctions[fc_nextSetSize - 1],
							   fc_tmpcontext)))
		{
			fc_aggstate->projected_set += 1;

			Assert(fc_aggstate->projected_set < fc_numGroupingSets);
			Assert(fc_nextSetSize > 0 || fc_aggstate->input_done);
		}
		else
		{
			/*
			 * 我们不再关心刚才投影的组，下一个
			 * 投影将始终是第一个（或唯一的）分组集
			 * （除非输入证明是空的）。
			 */
			fc_aggstate->projected_set = 0;

			/*
			 * 如果我们还没有新组的第一个元组，
			 * 则从外部计划中获取它。
			 */
			if (fc_aggstate->grp_firstTuple == NULL)
			{
				fc_outerslot = fc_fetch_input_tuple(fc_aggstate);
				if (!TupIsNull(fc_outerslot))
				{
					/*
					 * 复制第一个输入元组；我们将使用此
					 * 进行比较（在组模式中）和投影。
					 */
					fc_aggstate->grp_firstTuple = ExecCopySlotHeapTuple(fc_outerslot);
				}
				else
				{
					/* 外部计划完全没有产生任何元组 */
					if (fc_hasGroupingSets)
					{
						/*
						 * 如果根本没有输入，我们需要投影
						 * 行仅当存在大小为0的分组集。
						 * 注意，这意味着不能有对未分组变量的
						 * 引用，否则将导致空输出槽的问题。
						 *
						 * XXX：这不再成立，我们目前在finalize_aggregates()
						 * 中处理这个问题。
						 */
						fc_aggstate->input_done = true;

						while (fc_aggstate->phase->gset_lengths[fc_aggstate->projected_set] > 0)
						{
							fc_aggstate->projected_set += 1;
							if (fc_aggstate->projected_set >= fc_numGroupingSets)
							{
								/*
								 * 我们不能在这里设置agg_done，因为我们可能
								 * 还有更多阶段要做，即使输入为空。
								 * 所以我们需要重启整个外部循环。
								 */
								break;
							}
						}

						if (fc_aggstate->projected_set >= fc_numGroupingSets)
							continue;
					}
					else
					{
						fc_aggstate->agg_done = true;
						/* 如果我们在分组，则也不应生成任何元组 */
						if (fc_node->aggstrategy != AGG_PLAIN)
							return NULL;
					}
				}
			}

			/*
			 * 为新的输入元组组初始化工作状态。
			 */
			fc_initialize_aggregates(fc_aggstate, fc_pergroups, fc_numReset);

			if (fc_aggstate->grp_firstTuple != NULL)
			{
				/*
				 * 将复制的第一个输入元组存储在为其保留的元组表槽中。
				 * 该元组将在从槽中清除时删除。
				 */
				ExecForceStoreHeapTuple(fc_aggstate->grp_firstTuple,
										fc_firstSlot, true);
				fc_aggstate->grp_firstTuple = NULL;	/* 不要保留两个指针 */

				/* 为第一次调用advance_aggregates做准备 */
				fc_tmpcontext->ecxt_outertuple = fc_firstSlot;

				/*
				 * 处理每个外部计划元组，然后获取下一个，
				 * 直到我们耗尽外部计划或跨越组边界。
				 */
				for (;;)
				{
					/*
					 * 在混合聚合的阶段1中，我们还需要在
					 * advance_aggregates中更新哈希表。
					 */
					if (fc_aggstate->aggstrategy == AGG_MIXED &&
						fc_aggstate->current_phase == 1)
					{
						fc_lookup_hash_entries(fc_aggstate);
					}

					/* 向前推进聚合（或组合函数） */
					fc_advance_aggregates(fc_aggstate);

					/* 在每个元组后重置每个输入元组上下文 */
					ResetExprContext(fc_tmpcontext);

					fc_outerslot = fc_fetch_input_tuple(fc_aggstate);
					if (TupIsNull(fc_outerslot))
					{
						/* 不再有外部计划元组可用 */

						/* 如果我们构建了哈希表，则最终确定任何溢出 */
						if (fc_aggstate->aggstrategy == AGG_MIXED &&
							fc_aggstate->current_phase == 1)
							fc_hashagg_finish_initial_spills(fc_aggstate);

						if (fc_hasGroupingSets)
						{
							fc_aggstate->input_done = true;
							break;
						}
						else
						{
							fc_aggstate->agg_done = true;
							break;
						}
					}
					/* 为下一次advance_aggregates调用做准备 */
					fc_tmpcontext->ecxt_outertuple = fc_outerslot;

					/*
					 * 如果我们在分组，检查是否跨越了组
					 * 边界。
					 */
					if (fc_node->aggstrategy != AGG_PLAIN)
					{
						fc_tmpcontext->ecxt_innertuple = fc_firstSlot;
						if (!ExecQual(fc_aggstate->phase->eqfunctions[fc_node->numCols - 1],
									  fc_tmpcontext))
						{
							fc_aggstate->grp_firstTuple = ExecCopySlotHeapTuple(fc_outerslot);
							break;
						}
					}
				}
			}

			/*
			 * 使用代表性的输入元组进行对
			 * 非聚合输入列的引用，以聚合直接参数、节点
			 * qual和tlist。（如果我们不在分组，并且根本没有
			 * 输入行，我们将在此处带有空的firstSlot
			 * ... 但如果不是分组，则不能有对
			 * 非聚合输入列的引用，所以没有问题。）
			 */
			fc_econtext->ecxt_outertuple = fc_firstSlot;
		}

		Assert(fc_aggstate->projected_set >= 0);

		fc_currentSet = fc_aggstate->projected_set;

		fc_prepare_projection_slot(fc_aggstate, fc_econtext->ecxt_outertuple, fc_currentSet);

		fc_select_current_set(fc_aggstate, fc_currentSet, false);

		fc_finalize_aggregates(fc_aggstate,
							fc_peragg,
							fc_pergroups[fc_currentSet]);

		/*
		 * 如果现在没有行可投影，我们必须继续而
		 * 不是返回null，因为可能还有更多组。
		 */
		fc_result = fc_project_aggregates(fc_aggstate);
		if (fc_result)
			return fc_result;
	}

	/* 没有更多组 */
	return NULL;
}

/*
 * ExecAgg用于哈希情况：读取输入并构建哈希表
 */
static void fc_agg_fill_hash_table(AggState *fc_aggstate)
{
	TupleTableSlot *fc_outerslot;
	ExprContext *fc_tmpcontext = fc_aggstate->tmpcontext;

	/*
	 * 处理每个外部计划元组，然后获取下一个，直到我们
	 * 耗尽外部计划。
	 */
	for (;;)
	{
		fc_outerslot = fc_fetch_input_tuple(fc_aggstate);
		if (TupIsNull(fc_outerslot))
			break;

		/* 为lookup_hash_entries和advance_aggregates做准备 */
		fc_tmpcontext->ecxt_outertuple = fc_outerslot;

		/* 查找或构建哈希表条目 */
		fc_lookup_hash_entries(fc_aggstate);

		/* 向前推进聚合（或组合函数） */
		fc_advance_aggregates(fc_aggstate);

		/*
		 * 在每个元组之后重置每个输入元组上下文，但请注意哈希查找也会这样做
		 */
		ResetExprContext(fc_aggstate->tmpcontext);
	}

	/* 完成溢出处理（如果有） */
	fc_hashagg_finish_initial_spills(fc_aggstate);

	fc_aggstate->table_filled = true;
	/* 初始化以遍历第一个哈希表 */
	fc_select_current_set(fc_aggstate, 0, true);
	ResetTupleHashIterator(fc_aggstate->perhash[0].hashtable,
						   &fc_aggstate->perhash[0].hashiter);
}

/* 
 * 如果在哈希聚合过程中有数据溢出，重置哈希表并
 * 重新处理一批溢出数据。在重新处理一批之后，哈希
 * 表将再次包含数据，准备供 agg_retrieve_hash_table_in_memory() 消费。
 *
 * 仅在所有内存中的哈希表条目已完成并发出后才应调用。
 *
 * 当输入被耗尽且没有更多工作要做时返回 false；
 * 否则返回 true。
*/
static bool fc_agg_refill_hash_table(AggState *fc_aggstate)
{
	HashAggBatch *fc_batch;
	AggStatePerHash fc_perhash;
	HashAggSpill fc_spill;
	LogicalTapeSet *fc_tapeset = fc_aggstate->hash_tapeset;
	bool		fc_spill_initialized = false;

	if (fc_aggstate->hash_batches == NIL)
		return false;

	/* hash_batches 是一个栈，顶部项位于列表末尾 */
	fc_batch = llast(fc_aggstate->hash_batches);
	fc_aggstate->hash_batches = list_delete_last(fc_aggstate->hash_batches);

	hash_agg_set_limits(fc_aggstate->hashentrysize, fc_batch->input_card,
						fc_batch->used_bits, &fc_aggstate->hash_mem_limit,
						&fc_aggstate->hash_ngroups_limit, NULL);

	/* 
	 * 每批次只处理一个分组集；将其余设置为 NULL，以便
	 * advance_aggregates() 知道忽略它们。对于排序分组集，
	 * 我们在这里不触及 pergroups，因为如果后续需要重新扫描，
	 * 它们将是必需的。重新编译后，排序分组集的表达式
	 * 反正不会被评估。
	 */
	MemSet(fc_aggstate->hash_pergroup, 0,
		   sizeof(AggStatePerGroup) * fc_aggstate->num_hashes);

	/* 释放内存并重置哈希表 */
	ReScanExprContext(fc_aggstate->hashcontext);
	for (int fc_setno = 0; fc_setno < fc_aggstate->num_hashes; fc_setno++)
		ResetTupleHashTable(fc_aggstate->perhash[fc_setno].hashtable);

	fc_aggstate->hash_ngroups_current = 0;

	/* 
	 * 在 AGG_MIXED 模式下，哈希聚合在第 1 阶段进行，输出
	 * 在第 0 阶段进行。因此，在处理一批时切换到第 1 阶段，
	 * 在批处理完成后切换回第 0 阶段。
	 */
	Assert(fc_aggstate->current_phase == 0);
	if (fc_aggstate->phase->aggstrategy == AGG_MIXED)
	{
		fc_aggstate->current_phase = 1;
		fc_aggstate->phase = &fc_aggstate->phases[fc_aggstate->current_phase];
	}

	fc_select_current_set(fc_aggstate, fc_batch->setno, true);

	fc_perhash = &fc_aggstate->perhash[fc_aggstate->current_set];

	/* 
	 * 溢出的元组总是作为 MinimalTuples 读取，这可能与
	 * 外部计划不同，因此需要重新编译聚合表达式。
	 *
	 * 我们仍然需要 NULL 检查，因为我们一次只处理一个
	 * 分组集，其余将为 NULL。
	 */
	fc_hashagg_recompile_expressions(fc_aggstate, true, true);

	for (;;)
	{
		TupleTableSlot *fc_spillslot = fc_aggstate->hash_spill_rslot;
		TupleTableSlot *fc_hashslot = fc_perhash->hashslot;
		TupleHashEntry fc_entry;
		MinimalTuple fc_tuple;
		uint32		fc_hash;
		bool		fc_isnew = false;
		bool	   *fc_p_isnew = fc_aggstate->hash_spill_mode ? NULL : &fc_isnew;

		CHECK_FOR_INTERRUPTS();

		fc_tuple = fc_hashagg_batch_read(fc_batch, &fc_hash);
		if (fc_tuple == NULL)
			break;

		ExecStoreMinimalTuple(fc_tuple, fc_spillslot, true);
		fc_aggstate->tmpcontext->ecxt_outertuple = fc_spillslot;

		fc_prepare_hash_slot(fc_perhash,
						  fc_aggstate->tmpcontext->ecxt_outertuple,
						  fc_hashslot);
		fc_entry = LookupTupleHashEntryHash(fc_perhash->hashtable, fc_hashslot,
										 fc_p_isnew, fc_hash);

		if (fc_entry != NULL)
		{
			if (fc_isnew)
				fc_initialize_hash_entry(fc_aggstate, fc_perhash->hashtable, fc_entry);
			fc_aggstate->hash_pergroup[fc_batch->setno] = fc_entry->additional;
			fc_advance_aggregates(fc_aggstate);
		}
		else
		{
			if (!fc_spill_initialized)
			{
				/* 
				 * 避免在实际需要溢出之前初始化，以便
				 * 不分配将永远不会使用的磁带。
				 */
				fc_spill_initialized = true;
				fc_hashagg_spill_init(&fc_spill, fc_tapeset, fc_batch->used_bits,
								   fc_batch->input_card, fc_aggstate->hashentrysize);
			}
			/* 没有内存用于新组，进行溢出 */
			fc_hashagg_spill_tuple(fc_aggstate, &fc_spill, fc_spillslot, fc_hash);

			fc_aggstate->hash_pergroup[fc_batch->setno] = NULL;
		}

		/*
		 * 在每个元组之后重置每个输入元组上下文，但请注意哈希查找也会这样做
		 */
		ResetExprContext(fc_aggstate->tmpcontext);
	}

	LogicalTapeClose(fc_batch->input_tape);

	/* 切换回第 0 阶段 */
	fc_aggstate->current_phase = 0;
	fc_aggstate->phase = &fc_aggstate->phases[fc_aggstate->current_phase];

	if (fc_spill_initialized)
	{
		fc_hashagg_spill_finish(fc_aggstate, &fc_spill, fc_batch->setno);
		fc_hash_agg_update_metrics(fc_aggstate, true, fc_spill.npartitions);
	}
	else
		fc_hash_agg_update_metrics(fc_aggstate, true, 0);

	fc_aggstate->hash_spill_mode = false;

	/* 准备遍历第一个哈希表 */
	fc_select_current_set(fc_aggstate, fc_batch->setno, true);
	ResetTupleHashIterator(fc_aggstate->perhash[fc_batch->setno].hashtable,
						   &fc_aggstate->perhash[fc_batch->setno].hashiter);

	pfree(fc_batch);

	return true;
}

/* 
 * ExecAgg 的哈希情况：从哈希表中检索组
 *
 * 在耗尽内存元组后，也尝试使用先前溢出的元组重新填充
 * 哈希表。仅在所有内存和溢出元组都耗尽后返回 NULL。
*/
static TupleTableSlot * fc_agg_retrieve_hash_table(AggState *fc_aggstate)
{
	TupleTableSlot *fc_result = NULL;

	while (fc_result == NULL)
	{
		fc_result = fc_agg_retrieve_hash_table_in_memory(fc_aggstate);
		if (fc_result == NULL)
		{
			if (!fc_agg_refill_hash_table(fc_aggstate))
			{
				fc_aggstate->agg_done = true;
				break;
			}
		}
	}

	return fc_result;
}

/* 
 * 从内存哈希表中检索组，而不考虑任何溢出元组。
*/
static TupleTableSlot * fc_agg_retrieve_hash_table_in_memory(AggState *fc_aggstate)
{
	ExprContext *fc_econtext;
	AggStatePerAgg fc_peragg;
	AggStatePerGroup fc_pergroup;
	TupleHashEntryData *fc_entry;
	TupleTableSlot *fc_firstSlot;
	TupleTableSlot *fc_result;
	AggStatePerHash fc_perhash;

	/* 
	 * 从节点获取状态信息。
	 *
	 * econtext 是每个输出元组的表达式上下文。
	 */
	fc_econtext = fc_aggstate->ss.ps.ps_ExprContext;
	fc_peragg = fc_aggstate->peragg;
	fc_firstSlot = fc_aggstate->ss.ss_ScanTupleSlot;

	/* 
	 * 注意，perhash（因此通过它访问的任何内容）可以
	 * 在循环内变化，因为我们在分组集之间切换。
	 */
	fc_perhash = &fc_aggstate->perhash[fc_aggstate->current_set];

	/* 
	 * 我们循环检索组，直到找到一个满足
	 * aggstate->ss.ps.qual
*/
	for (;;)
	{
		TupleTableSlot *fc_hashslot = fc_perhash->hashslot;
		int			fc_i;

		CHECK_FOR_INTERRUPTS();

		/* 
		 * 找到哈希表中的下一个条目
*/
		fc_entry = ScanTupleHashTable(fc_perhash->hashtable, &fc_perhash->hashiter);
		if (fc_entry == NULL)
		{
			int			fc_nextset = fc_aggstate->current_set + 1;

			if (fc_nextset < fc_aggstate->num_hashes)
			{
				/* 
				 * 切换到下一个分组集，重新初始化，并重新开始
				 * 循环。
*/
				fc_select_current_set(fc_aggstate, fc_nextset, true);

				fc_perhash = &fc_aggstate->perhash[fc_aggstate->current_set];

				ResetTupleHashIterator(fc_perhash->hashtable, &fc_perhash->hashiter);

				continue;
			}
			else
			{
				return NULL;
			}
		}

		/* 
		 * 清除每个组的每个输出元组上下文
		 *
		 * 我们故意不在这里使用 ReScanExprContext；如果任何聚合
		 * 已注册关闭回调，它们现在不能被调用，因为我们
		 * 可能还没有完成那个聚合。
		 */
		ResetExprContext(fc_econtext);

		/* 
		 * 将代表性元组转换回具有正确列的元组。
*/
		ExecStoreMinimalTuple(fc_entry->firstTuple, fc_hashslot, false);
		slot_getallattrs(fc_hashslot);

		ExecClearTuple(fc_firstSlot);
		memset(fc_firstSlot->tts_isnull, true,
			   fc_firstSlot->tts_tupleDescriptor->natts * sizeof(bool));

		for (fc_i = 0; fc_i < fc_perhash->numhashGrpCols; fc_i++)
		{
			int			fc_varNumber = fc_perhash->hashGrpColIdxInput[fc_i] - 1;

			fc_firstSlot->tts_values[fc_varNumber] = fc_hashslot->tts_values[fc_i];
			fc_firstSlot->tts_isnull[fc_varNumber] = fc_hashslot->tts_isnull[fc_i];
		}
		ExecStoreVirtualTuple(fc_firstSlot);

		fc_pergroup = (AggStatePerGroup) fc_entry->additional;

		/* 
		 * 使用代表性输入元组进行对 qual 和 tlist 中的
		 * 非聚合输入列的任何引用。
*/
		fc_econtext->ecxt_outertuple = fc_firstSlot;

		fc_prepare_projection_slot(fc_aggstate,
								fc_econtext->ecxt_outertuple,
								fc_aggstate->current_set);

		fc_finalize_aggregates(fc_aggstate, fc_peragg, fc_pergroup);

		fc_result = fc_project_aggregates(fc_aggstate);
		if (fc_result)
			return fc_result;
	}

	/* 没有更多组 */
	return NULL;
}

/* 
 * hashagg_spill_init
 *
 * 在确定溢出是必要后调用。选择要创建的分区数量，
 * 并初始化它们。
*/
static void fc_hashagg_spill_init(HashAggSpill *fc_spill, LogicalTapeSet *fc_tapeset, int fc_used_bits,
				   double fc_input_groups, double fc_hashentrysize)
{
	int			fc_npartitions;
	int			fc_partition_bits;

	fc_npartitions = fc_hash_choose_num_partitions(fc_input_groups, fc_hashentrysize,
											 fc_used_bits, &fc_partition_bits);

	fc_spill->partitions = palloc0(sizeof(LogicalTape *) * fc_npartitions);
	fc_spill->ntuples = palloc0(sizeof(int64) * fc_npartitions);
	fc_spill->hll_card = palloc0(sizeof(hyperLogLogState) * fc_npartitions);

	for (int fc_i = 0; fc_i < fc_npartitions; fc_i++)
		fc_spill->partitions[fc_i] = LogicalTapeCreate(fc_tapeset);

	fc_spill->shift = 32 - fc_used_bits - fc_partition_bits;
	fc_spill->mask = (fc_npartitions - 1) << fc_spill->shift;
	fc_spill->npartitions = fc_npartitions;

	for (int fc_i = 0; fc_i < fc_npartitions; fc_i++)
		initHyperLogLog(&fc_spill->hll_card[fc_i], HASHAGG_HLL_BIT_WIDTH);
}

/* 
 * hashagg_spill_tuple
 *
 * 哈希表中没有新组的空间。保存到适当的分区中以供后用。
*/
static Size fc_hashagg_spill_tuple(AggState *fc_aggstate, HashAggSpill *fc_spill,
					TupleTableSlot *fc_inputslot, uint32 fc_hash)
{
	TupleTableSlot *fc_spillslot;
	int			fc_partition;
	MinimalTuple fc_tuple;
	LogicalTape *fc_tape;
	int			fc_total_written = 0;
	bool		fc_shouldFree;

	Assert(fc_spill->partitions != NULL);

	/* 仅溢出我们实际需要的属性 */
	if (!fc_aggstate->all_cols_needed)
	{
		fc_spillslot = fc_aggstate->hash_spill_wslot;
		slot_getsomeattrs(fc_inputslot, fc_aggstate->max_colno_needed);
		ExecClearTuple(fc_spillslot);
		for (int fc_i = 0; fc_i < fc_spillslot->tts_tupleDescriptor->natts; fc_i++)
		{
			if (bms_is_member(fc_i + 1, fc_aggstate->colnos_needed))
			{
				fc_spillslot->tts_values[fc_i] = fc_inputslot->tts_values[fc_i];
				fc_spillslot->tts_isnull[fc_i] = fc_inputslot->tts_isnull[fc_i];
			}
			else
				fc_spillslot->tts_isnull[fc_i] = true;
		}
		ExecStoreVirtualTuple(fc_spillslot);
	}
	else
		fc_spillslot = fc_inputslot;

	fc_tuple = ExecFetchSlotMinimalTuple(fc_spillslot, &fc_shouldFree);

	fc_partition = (fc_hash & fc_spill->mask) >> fc_spill->shift;
	fc_spill->ntuples[fc_partition]++;

	/* 
	 * 所有用于给定分区的哈希值都有一些共同位，这导致
	 * 不良的 HLL 基数估计。对哈希进行哈希以获得更均匀的分布。
	 */
	addHyperLogLog(&fc_spill->hll_card[fc_partition], hash_bytes_uint32(fc_hash));

	fc_tape = fc_spill->partitions[fc_partition];

	LogicalTapeWrite(fc_tape, (void *) &fc_hash, sizeof(uint32));
	fc_total_written += sizeof(uint32);

	LogicalTapeWrite(fc_tape, (void *) fc_tuple, fc_tuple->t_len);
	fc_total_written += fc_tuple->t_len;

	if (fc_shouldFree)
		pfree(fc_tuple);

	return fc_total_written;
}

/* 
 * hashagg_batch_new
 *
 * 构造一个 HashAggBatch 项，表示要进行的 HashAgg
 * 的一个迭代。
*/
static HashAggBatch *
fc_hashagg_batch_new(LogicalTape *fc_input_tape, int fc_setno,
				  int64 fc_input_tuples, double fc_input_card, int fc_used_bits)
{
	HashAggBatch *fc_batch = palloc0(sizeof(HashAggBatch));

	fc_batch->setno = fc_setno;
	fc_batch->used_bits = fc_used_bits;
	fc_batch->input_tape = fc_input_tape;
	fc_batch->input_tuples = fc_input_tuples;
	fc_batch->input_card = fc_input_card;

	return fc_batch;
}

/* 
 * read_spilled_tuple
 * 		从批处理的磁带中读取下一个元组。如果没有更多，返回 NULL。
*/
static MinimalTuple fc_hashagg_batch_read(HashAggBatch *fc_batch, uint32 *fc_hashp)
{
	LogicalTape *fc_tape = fc_batch->input_tape;
	MinimalTuple fc_tuple;
	uint32		fc_t_len;
	size_t		fc_nread;
	uint32		fc_hash;

	fc_nread = LogicalTapeRead(fc_tape, &fc_hash, sizeof(uint32));
	if (fc_nread == 0)
		return NULL;
	if (fc_nread != sizeof(uint32))
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg_internal("unexpected EOF for tape %p: requested %zu bytes, read %zu bytes",
								 fc_tape, sizeof(uint32), fc_nread)));
	if (fc_hashp != NULL)
		*fc_hashp = fc_hash;

	fc_nread = LogicalTapeRead(fc_tape, &fc_t_len, sizeof(fc_t_len));
	if (fc_nread != sizeof(uint32))
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg_internal("unexpected EOF for tape %p: requested %zu bytes, read %zu bytes",
								 fc_tape, sizeof(uint32), fc_nread)));

	fc_tuple = (MinimalTuple) palloc(fc_t_len);
	fc_tuple->t_len = fc_t_len;

	fc_nread = LogicalTapeRead(fc_tape,
							(void *) ((char *) fc_tuple + sizeof(uint32)),
							fc_t_len - sizeof(uint32));
	if (fc_nread != fc_t_len - sizeof(uint32))
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg_internal("unexpected EOF for tape %p: requested %zu bytes, read %zu bytes",
								 fc_tape, fc_t_len - sizeof(uint32), fc_nread)));

	return fc_tuple;
}

/* 
 * hashagg_finish_initial_spills
 *
 * 在处理完 HashAggBatch 后，可能会将元组溢出到
 * 磁盘。如果是这样，将溢出的分区转化为必须稍后执行的新批次。
*/
static void fc_hashagg_finish_initial_spills(AggState *fc_aggstate)
{
	int			fc_setno;
	int			fc_total_npartitions = 0;

	if (fc_aggstate->hash_spills != NULL)
	{
		for (fc_setno = 0; fc_setno < fc_aggstate->num_hashes; fc_setno++)
		{
			HashAggSpill *fc_spill = &fc_aggstate->hash_spills[fc_setno];

			fc_total_npartitions += fc_spill->npartitions;
			fc_hashagg_spill_finish(fc_aggstate, fc_spill, fc_setno);
		}

		/* 
		 * 我们不再处理外部计划中的元组；仅
		 * 处理溢出元组的批次。初始溢出结构
		 * 不再需要。
*/
		pfree(fc_aggstate->hash_spills);
		fc_aggstate->hash_spills = NULL;
	}

	fc_hash_agg_update_metrics(fc_aggstate, false, fc_total_npartitions);
	fc_aggstate->hash_spill_mode = false;
}

/*
 * hashagg_spill_finish
 *
 * 转换溢出分区为新的批次。
 */
static void fc_hashagg_spill_finish(AggState *fc_aggstate, HashAggSpill *fc_spill, int fc_setno)
{
	int			fc_i;
	int			fc_used_bits = 32 - fc_spill->shift;

	if (fc_spill->npartitions == 0)
		return;					/* 没有溢出 */

	for (fc_i = 0; fc_i < fc_spill->npartitions; fc_i++)
	{
		LogicalTape *fc_tape = fc_spill->partitions[fc_i];
		HashAggBatch *fc_new_batch;
		double		fc_cardinality;

		/* 如果分区为空，则不创建新的工作批次 */
		if (fc_spill->ntuples[fc_i] == 0)
			continue;

		fc_cardinality = estimateHyperLogLog(&fc_spill->hll_card[fc_i]);
		freeHyperLogLog(&fc_spill->hll_card[fc_i]);

		/* 回放在未使用时释放缓冲区 */
		LogicalTapeRewindForRead(fc_tape, HASHAGG_READ_BUFFER_SIZE);

		fc_new_batch = fc_hashagg_batch_new(fc_tape, fc_setno,
									  fc_spill->ntuples[fc_i], fc_cardinality,
									  fc_used_bits);
		fc_aggstate->hash_batches = lappend(fc_aggstate->hash_batches, fc_new_batch);
		fc_aggstate->hash_batches_used++;
	}

	pfree(fc_spill->ntuples);
	pfree(fc_spill->hll_card);
	pfree(fc_spill->partitions);
}

/*
 * 释放与溢出 HashAgg 相关的资源。
 */
static void fc_hashagg_reset_spill_state(AggState *fc_aggstate)
{
	/* 从初始传递中释放溢出 */
	if (fc_aggstate->hash_spills != NULL)
	{
		int			fc_setno;

		for (fc_setno = 0; fc_setno < fc_aggstate->num_hashes; fc_setno++)
		{
			HashAggSpill *fc_spill = &fc_aggstate->hash_spills[fc_setno];

			pfree(fc_spill->ntuples);
			pfree(fc_spill->partitions);
		}
		pfree(fc_aggstate->hash_spills);
		fc_aggstate->hash_spills = NULL;
	}

	/* 释放批次 */
	list_free_deep(fc_aggstate->hash_batches);
	fc_aggstate->hash_batches = NIL;

	/* 关闭磁带集 */
	if (fc_aggstate->hash_tapeset != NULL)
	{
		LogicalTapeSetClose(fc_aggstate->hash_tapeset);
		fc_aggstate->hash_tapeset = NULL;
	}
}


/* -----------------
 * ExecInitAgg
 *
 * 为规划器生成的 agg 节点创建运行时信息，并初始化其外部子树。
 *
 * -----------------
 */
AggState *
ExecInitAgg(Agg *fc_node, EState *fc_estate, int fc_eflags)
{
	AggState   *fc_aggstate;
	AggStatePerAgg fc_peraggs;
	AggStatePerTrans fc_pertransstates;
	AggStatePerGroup *fc_pergroups;
	Plan	   *fc_outerPlan;
	ExprContext *fc_econtext;
	TupleDesc	fc_scanDesc;
	int			fc_max_aggno;
	int			fc_max_transno;
	int			fc_numaggrefs;
	int			fc_numaggs;
	int			fc_numtrans;
	int			fc_phase;
	int			fc_phaseidx;
	ListCell   *fc_l;
	Bitmapset  *fc_all_grouped_cols = NULL;
	int			fc_numGroupingSets = 1;
	int			fc_numPhases;
	int			fc_numHashes;
	int			fc_i = 0;
	int			fc_j = 0;
	bool		fc_use_hashing = (fc_node->aggstrategy == AGG_HASHED ||
							   fc_node->aggstrategy == AGG_MIXED);

	/* 检查不支持的标志 */
	Assert(!(fc_eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));

	/*
	 * 创建状态结构
	 */
	fc_aggstate = makeNode(AggState);
	fc_aggstate->ss.ps.plan = (Plan *) fc_node;
	fc_aggstate->ss.ps.state = fc_estate;
	fc_aggstate->ss.ps.ExecProcNode = fc_ExecAgg;

	fc_aggstate->aggs = NIL;
	fc_aggstate->numaggs = 0;
	fc_aggstate->numtrans = 0;
	fc_aggstate->aggstrategy = fc_node->aggstrategy;
	fc_aggstate->aggsplit = fc_node->aggsplit;
	fc_aggstate->maxsets = 0;
	fc_aggstate->projected_set = -1;
	fc_aggstate->current_set = 0;
	fc_aggstate->peragg = NULL;
	fc_aggstate->pertrans = NULL;
	fc_aggstate->curperagg = NULL;
	fc_aggstate->curpertrans = NULL;
	fc_aggstate->input_done = false;
	fc_aggstate->agg_done = false;
	fc_aggstate->pergroups = NULL;
	fc_aggstate->grp_firstTuple = NULL;
	fc_aggstate->sort_in = NULL;
	fc_aggstate->sort_out = NULL;

	/*
	 * phases[0] 始终存在，但在排序/普通模式下是虚拟的
	 */
	fc_numPhases = (fc_use_hashing ? 1 : 2);
	fc_numHashes = (fc_use_hashing ? 1 : 0);

	/*
	 * 计算任何阶段中分组集的最大数量；这决定了一些分配的大小。 还要计算阶段的数量，因为所有哈希/混合节点仅贡献一个阶段。
	 */
	if (fc_node->groupingSets)
	{
		fc_numGroupingSets = list_length(fc_node->groupingSets);

		foreach(fc_l, fc_node->chain)
		{
			Agg		   *fc_agg = lfirst(fc_l);

			fc_numGroupingSets = Max(fc_numGroupingSets,
								  list_length(fc_agg->groupingSets));

			/*
			 * 额外的 AGG_HASHED 聚合成为阶段 0 的一部分，但所有其他的增加一个额外的阶段。
			 */
			if (fc_agg->aggstrategy != AGG_HASHED)
				++fc_numPhases;
			else
				++fc_numHashes;
		}
	}

	fc_aggstate->maxsets = fc_numGroupingSets;
	fc_aggstate->numphases = fc_numPhases;

	fc_aggstate->aggcontexts = (ExprContext **)
		palloc0(sizeof(ExprContext *) * fc_numGroupingSets);

	/*
	 * 创建表达式上下文。我们需要三个或更多，一个用于每输入元组处理，一个用于每输出元组处理，一个用于所有哈希表，以及一个用于每个分组集。每个分组集的每元组内存上下文（aggcontexts）替代了以前用于保存过渡值的独立内存上下文。我们通过使用 ExecAssignExprContext() 来构建它们，稍微“作弊”一下。
	 *
	 * 注意：aggcontexts 中存储的内容和常规每查询内存上下文中存储的内容的细节是由一个简单的决定驱动的：我们希望在组边界（如果不进行哈希）和 ExecReScanAgg 中重置 aggcontext 以恢复不再需要的空间。
	 */
	ExecAssignExprContext(fc_estate, &fc_aggstate->ss.ps);
	fc_aggstate->tmpcontext = fc_aggstate->ss.ps.ps_ExprContext;

	for (fc_i = 0; fc_i < fc_numGroupingSets; ++fc_i)
	{
		ExecAssignExprContext(fc_estate, &fc_aggstate->ss.ps);
		fc_aggstate->aggcontexts[fc_i] = fc_aggstate->ss.ps.ps_ExprContext;
	}

	if (fc_use_hashing)
		fc_aggstate->hashcontext = CreateWorkExprContext(fc_estate);

	ExecAssignExprContext(fc_estate, &fc_aggstate->ss.ps);

	/*
	 * 初始化子节点。
	 *
	 * 如果我们执行哈希聚合，那么子计划不需要有效地处理回放；见 ExecReScanAgg。
	 */
	if (fc_node->aggstrategy == AGG_HASHED)
		fc_eflags &= ~EXEC_FLAG_REWIND;
	fc_outerPlan = outerPlan(fc_node);
	outerPlanState(fc_aggstate) = ExecInitNode(fc_outerPlan, fc_estate, fc_eflags);

	/*
	 * 初始化源元组类型。
	 */
	fc_aggstate->ss.ps.outerops =
		ExecGetResultSlotOps(outerPlanState(&fc_aggstate->ss),
							 &fc_aggstate->ss.ps.outeropsfixed);
	fc_aggstate->ss.ps.outeropsset = true;

	ExecCreateScanSlotFromOuterPlan(fc_estate, &fc_aggstate->ss,
									fc_aggstate->ss.ps.outerops);
	fc_scanDesc = fc_aggstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;

	/*
	 * 如果有超过两个阶段（包括潜在的虚拟阶段 0），输入将使用 tuplesort 重新排序。这需要一个插槽。
	 */
	if (fc_numPhases > 2)
	{
		fc_aggstate->sort_slot = ExecInitExtraTupleSlot(fc_estate, fc_scanDesc,
													 &TTSOpsMinimalTuple);

		/*
		 * tuplesort 的输出，以及外部子级的输出可能不使用相同类型的插槽。在大多数情况下，子节点将是一个 Sort，因此将返回一个 TTSOpsMinimalTuple 类型的插槽 - 但输入也可能由于索引而被预排序，在这种情况下它可能是不同类型的插槽。
		 *
		 * XXX：为了提高效率，最好同时生成具有相应 outerops* 设置的表达式，以便适应单独的阶段 - 变形通常是具有大量每组行的聚合的瓶颈。如果有多个排序，我们知道除了第一个外，所有都使用 TTSOpsMinimalTuple（通过 nodeAgg.c 内部 tuplesort）。
		 */
		if (fc_aggstate->ss.ps.outeropsfixed &&
			fc_aggstate->ss.ps.outerops != &TTSOpsMinimalTuple)
			fc_aggstate->ss.ps.outeropsfixed = false;
	}

	/*
	 * 初始化结果类型、插槽和投影。
	 */
	ExecInitResultTupleSlotTL(&fc_aggstate->ss.ps, &TTSOpsVirtual);
	ExecAssignProjectionInfo(&fc_aggstate->ss.ps, NULL);

	/*
	 * 初始化子表达式
	 *
	 * 我们预计解析器已经检查过没有聚合包含其参数中的其他聚合调用（为了确保，我们在初始化计划节点时再次验证它）。在 SQL 语义下，这毫无意义，规范中禁止。因为这是事实，我们无需担心以任何特定顺序评估聚合。
	 *
	 * 注意：execExpr.c 为我们找到 Aggrefs，并将它们添加到 aggstate->aggs。qual 中的 Aggrefs 在这里找到；targetlist 中的 Aggrefs 在上面的 ExecAssignProjectionInfo 中找到。
	 */
	fc_aggstate->ss.ps.qual =
		ExecInitQual(fc_node->plan.qual, (PlanState *) fc_aggstate);

	/*
	 * 现在我们应该已经在 targetlist 和 quals 中找到所有 Aggrefs。
	 */
	fc_numaggrefs = list_length(fc_aggstate->aggs);
	fc_max_aggno = -1;
	fc_max_transno = -1;
	foreach(fc_l, fc_aggstate->aggs)
	{
		Aggref	   *fc_aggref = (Aggref *) lfirst(fc_l);

		fc_max_aggno = Max(fc_max_aggno, fc_aggref->aggno);
		fc_max_transno = Max(fc_max_transno, fc_aggref->aggtransno);
	}
	fc_numaggs = fc_max_aggno + 1;
	fc_numtrans = fc_max_transno + 1;

	/*
	 * 对于每个阶段，准备分组集数据和用于比较函数的 fmgr 查找数据。在传递中累积所有_grouped_cols。
	 */
	fc_aggstate->phases = palloc0(fc_numPhases * sizeof(AggStatePerPhaseData));

	fc_aggstate->num_hashes = fc_numHashes;
	if (fc_numHashes)
	{
		fc_aggstate->perhash = palloc0(sizeof(AggStatePerHashData) * fc_numHashes);
		fc_aggstate->phases[0].numsets = 0;
		fc_aggstate->phases[0].gset_lengths = palloc(fc_numHashes * sizeof(int));
		fc_aggstate->phases[0].grouped_cols = palloc(fc_numHashes * sizeof(Bitmapset *));
	}

	fc_phase = 0;
	for (fc_phaseidx = 0; fc_phaseidx <= list_length(fc_node->chain); ++fc_phaseidx)
	{
		Agg		   *fc_aggnode;
		Sort	   *fc_sortnode;

		if (fc_phaseidx > 0)
		{
			fc_aggnode = list_nth_node(Agg, fc_node->chain, fc_phaseidx - 1);
			fc_sortnode = castNode(Sort, fc_aggnode->plan.lefttree);
		}
		else
		{
			fc_aggnode = fc_node;
			fc_sortnode = NULL;
		}

		Assert(fc_phase <= 1 || fc_sortnode);

		if (fc_aggnode->aggstrategy == AGG_HASHED
			|| fc_aggnode->aggstrategy == AGG_MIXED)
		{
			AggStatePerPhase fc_phasedata = &fc_aggstate->phases[0];
			AggStatePerHash fc_perhash;
			Bitmapset  *fc_cols = NULL;

			Assert(fc_phase == 0);
			fc_i = fc_phasedata->numsets++;
			fc_perhash = &fc_aggstate->perhash[fc_i];

			/* 阶段 0 始终指向哈希情况下的“真实” Agg */
			fc_phasedata->aggnode = fc_node;
			fc_phasedata->aggstrategy = fc_node->aggstrategy;

			/* 但表示此哈希的实际 Agg 节点保存在这里 */
			fc_perhash->aggnode = fc_aggnode;

			fc_phasedata->gset_lengths[fc_i] = fc_perhash->numCols = fc_aggnode->numCols;

			for (fc_j = 0; fc_j < fc_aggnode->numCols; ++fc_j)
				fc_cols = bms_add_member(fc_cols, fc_aggnode->grpColIdx[fc_j]);

			fc_phasedata->grouped_cols[fc_i] = fc_cols;

			fc_all_grouped_cols = bms_add_members(fc_all_grouped_cols, fc_cols);
			continue;
		}
		else
		{
			AggStatePerPhase fc_phasedata = &fc_aggstate->phases[++fc_phase];
			int			fc_num_sets;

			fc_phasedata->numsets = fc_num_sets = list_length(fc_aggnode->groupingSets);

			if (fc_num_sets)
			{
				fc_phasedata->gset_lengths = palloc(fc_num_sets * sizeof(int));
				fc_phasedata->grouped_cols = palloc(fc_num_sets * sizeof(Bitmapset *));

				fc_i = 0;
				foreach(fc_l, fc_aggnode->groupingSets)
				{
					int			fc_current_length = list_length(lfirst(fc_l));
					Bitmapset  *fc_cols = NULL;

					/* 规划器强制这一点正确 */
					for (fc_j = 0; fc_j < fc_current_length; ++fc_j)
						fc_cols = bms_add_member(fc_cols, fc_aggnode->grpColIdx[fc_j]);

					fc_phasedata->grouped_cols[fc_i] = fc_cols;
					fc_phasedata->gset_lengths[fc_i] = fc_current_length;

					++fc_i;
				}

				fc_all_grouped_cols = bms_add_members(fc_all_grouped_cols,
												   fc_phasedata->grouped_cols[0]);
			}
			else
			{
				Assert(fc_phaseidx == 0);

				fc_phasedata->gset_lengths = NULL;
				fc_phasedata->grouped_cols = NULL;
			}

			/*
			 * 如果我们正在分组，预计算用于内部循环的 fmgr 查找数据。
			 */
			if (fc_aggnode->aggstrategy == AGG_SORTED)
			{
				int			fc_i = 0;

				Assert(fc_aggnode->numCols > 0);

				/*
				 * 为每个需要比较的列子集构建单独的函数。
				 */
				fc_phasedata->eqfunctions =
					(ExprState **) palloc0(fc_aggnode->numCols * sizeof(ExprState *));

				/* 对于每个分组集 */
				for (fc_i = 0; fc_i < fc_phasedata->numsets; fc_i++)
				{
					int			fc_length = fc_phasedata->gset_lengths[fc_i];

					/* 对于空分组集没有要做的事情 */
					if (fc_length == 0)
						continue;

					
/* 如果我们已经有一个这个长度的，它就可以了 */
					if (fc_phasedata->eqfunctions[fc_length - 1] != NULL)
						continue;

					fc_phasedata->eqfunctions[fc_length - 1] =
						execTuplesMatchPrepare(fc_scanDesc,
											   fc_length,
											   fc_aggnode->grpColIdx,
											   fc_aggnode->grpOperators,
											   fc_aggnode->grpCollations,
											   (PlanState *) fc_aggstate);
				}

				/* 并且对于所有分组列，除非已经计算过 */
				if (fc_phasedata->eqfunctions[fc_aggnode->numCols - 1] == NULL)
				{
					fc_phasedata->eqfunctions[fc_aggnode->numCols - 1] =
						execTuplesMatchPrepare(fc_scanDesc,
											   fc_aggnode->numCols,
											   fc_aggnode->grpColIdx,
											   fc_aggnode->grpOperators,
											   fc_aggnode->grpCollations,
											   (PlanState *) fc_aggstate);
				}
			}

			fc_phasedata->aggnode = fc_aggnode;
			fc_phasedata->aggstrategy = fc_aggnode->aggstrategy;
			fc_phasedata->sortnode = fc_sortnode;
		}
	}

	/*
	 * 将所有分组列转换为一个降序列表。
	 */
	fc_i = -1;
	while ((fc_i = bms_next_member(fc_all_grouped_cols, fc_i)) >= 0)
		fc_aggstate->all_grouped_cols = lcons_int(fc_i, fc_aggstate->all_grouped_cols);

	/*
	 * 在输出表达式上下文中设置聚合结果存储，并且
	 * 分配我私有的每个聚合工作存储
	 */
	fc_econtext = fc_aggstate->ss.ps.ps_ExprContext;
	fc_econtext->ecxt_aggvalues = (Datum *) palloc0(sizeof(Datum) * fc_numaggs);
	fc_econtext->ecxt_aggnulls = (bool *) palloc0(sizeof(bool) * fc_numaggs);

	fc_peraggs = (AggStatePerAgg) palloc0(sizeof(AggStatePerAggData) * fc_numaggs);
	fc_pertransstates = (AggStatePerTrans) palloc0(sizeof(AggStatePerTransData) * fc_numtrans);

	fc_aggstate->peragg = fc_peraggs;
	fc_aggstate->pertrans = fc_pertransstates;


	fc_aggstate->all_pergroups =
		(AggStatePerGroup *) palloc0(sizeof(AggStatePerGroup)
									 * (fc_numGroupingSets + fc_numHashes));
	fc_pergroups = fc_aggstate->all_pergroups;

	if (fc_node->aggstrategy != AGG_HASHED)
	{
		for (fc_i = 0; fc_i < fc_numGroupingSets; fc_i++)
		{
			fc_pergroups[fc_i] = (AggStatePerGroup) palloc0(sizeof(AggStatePerGroupData)
													  * fc_numaggs);
		}

		fc_aggstate->pergroups = fc_pergroups;
		fc_pergroups += fc_numGroupingSets;
	}

	/*
	 * 哈希只能出现在初始阶段。
	 */
	if (fc_use_hashing)
	{
		Plan	   *fc_outerplan = outerPlan(fc_node);
		uint64		fc_totalGroups = 0;
		int			fc_i;

		fc_aggstate->hash_metacxt = AllocSetContextCreate(fc_aggstate->ss.ps.state->es_query_cxt,
													   "HashAgg meta context",
													   ALLOCSET_DEFAULT_SIZES);
		fc_aggstate->hash_spill_rslot = ExecInitExtraTupleSlot(fc_estate, fc_scanDesc,
															&TTSOpsMinimalTuple);
		fc_aggstate->hash_spill_wslot = ExecInitExtraTupleSlot(fc_estate, fc_scanDesc,
															&TTSOpsVirtual);

		/* 这是一个指针数组，而不是结构体 */
		fc_aggstate->hash_pergroup = fc_pergroups;

		fc_aggstate->hashentrysize = hash_agg_entry_size(fc_aggstate->numtrans,
													  fc_outerplan->plan_width,
													  fc_node->transitionSpace);

		/*
		 * 在设定限制和估计分区数量时，考虑所有分组集。
		 * 当有多个分组集时，这可能不准确，但仍然应该是
		 * 合理的。
		 */
		for (fc_i = 0; fc_i < fc_aggstate->num_hashes; fc_i++)
			fc_totalGroups += fc_aggstate->perhash[fc_i].aggnode->numGroups;

		hash_agg_set_limits(fc_aggstate->hashentrysize, fc_totalGroups, 0,
							&fc_aggstate->hash_mem_limit,
							&fc_aggstate->hash_ngroups_limit,
							&fc_aggstate->hash_planned_partitions);
		fc_find_hash_columns(fc_aggstate);

		/* 如果我们只是做 EXPLAIN，跳过大规模内存分配 */
		if (!(fc_eflags & EXEC_FLAG_EXPLAIN_ONLY))
			fc_build_hash_tables(fc_aggstate);

		fc_aggstate->table_filled = false;

		/* 先将其初始化为 1，表示尚未溢出 */
		fc_aggstate->hash_batches_used = 1;
	}

	/*
	 * 将当前阶段依赖值初始化为初始阶段。初始
	 * 阶段为 1（第一次排序通行）对于所有使用排序的策略（如果
	 * 也进行了哈希，则阶段 0 最后处理）；但如果只是
	 * 进行哈希，则只有阶段 0。
	 */
	if (fc_node->aggstrategy == AGG_HASHED)
	{
		fc_aggstate->current_phase = 0;
		fc_initialize_phase(fc_aggstate, 0);
		fc_select_current_set(fc_aggstate, 0, true);
	}
	else
	{
		fc_aggstate->current_phase = 1;
		fc_initialize_phase(fc_aggstate, 1);
		fc_select_current_set(fc_aggstate, 0, false);
	}

	/*
	 * 执行聚合函数信息的查找，并初始化
	 * 每个聚合和每个转换数据的不变字段。
	 */
	foreach(fc_l, fc_aggstate->aggs)
	{
		Aggref	   *fc_aggref = lfirst(fc_l);
		AggStatePerAgg fc_peragg;
		AggStatePerTrans fc_pertrans;
		Oid			fc_aggTransFnInputTypes[FUNC_MAX_ARGS];
		int			fc_numAggTransFnArgs;
		int			fc_numDirectArgs;
		HeapTuple	fc_aggTuple;
		Form_pg_aggregate fc_aggform;
		AclResult	fc_aclresult;
		Oid			fc_finalfn_oid;
		Oid			fc_serialfn_oid,
					fc_deserialfn_oid;
		Oid			fc_aggOwner;
		Expr	   *fc_finalfnexpr;
		Oid			fc_aggtranstype;

		/* 计划器应该已经将聚合分配到正确的级别 */
		Assert(fc_aggref->agglevelsup == 0);
		/* ... 并且拆分模式应该匹配 */
		Assert(fc_aggref->aggsplit == fc_aggstate->aggsplit);

		fc_peragg = &fc_peraggs[fc_aggref->aggno];

		/* 检查我们是否已经为该聚合初始化了状态。 */
		if (fc_peragg->aggref != NULL)
			continue;

		fc_peragg->aggref = fc_aggref;
		fc_peragg->transno = fc_aggref->aggtransno;

		/* 获取 pg_aggregate 行 */
		fc_aggTuple = SearchSysCache1(AGGFNOID,
								   ObjectIdGetDatum(fc_aggref->aggfnoid));
		if (!HeapTupleIsValid(fc_aggTuple))
			elog(ERROR, "cache lookup failed for aggregate %u",
				 fc_aggref->aggfnoid);
		fc_aggform = (Form_pg_aggregate) GETSTRUCT(fc_aggTuple);

		/* 检查调用聚合函数的权限 */
		fc_aclresult = pg_proc_aclcheck(fc_aggref->aggfnoid, GetUserId(),
									 ACL_EXECUTE);
		if (fc_aclresult != ACLCHECK_OK)
			aclcheck_error(fc_aclresult, OBJECT_AGGREGATE,
						   get_func_name(fc_aggref->aggfnoid));
		InvokeFunctionExecuteHook(fc_aggref->aggfnoid);

		/* 计划器在 Aggref 本身中记录了转换状态类型 */
		fc_aggtranstype = fc_aggref->aggtranstype;
		Assert(OidIsValid(fc_aggtranstype));

		/* 仅在我们正在最终聚合时需要最终函数 */
		if (DO_AGGSPLIT_SKIPFINAL(fc_aggstate->aggsplit))
			fc_peragg->finalfn_oid = fc_finalfn_oid = InvalidOid;
		else
			fc_peragg->finalfn_oid = fc_finalfn_oid = fc_aggform->aggfinalfn;

		fc_serialfn_oid = InvalidOid;
		fc_deserialfn_oid = InvalidOid;

		/*
		 * 检查是否需要序列化/反序列化。我们仅对具有
		 * transtype INTERNAL 的聚合进行此操作。
		 */
		if (fc_aggtranstype == INTERNALOID)
		{
			/*
			 * 计划器仅在每个具有 INTERNAL 状态的聚合
			 * 具有序列化函数时生成序列化聚合节点。验证这一点。
			 */
			if (DO_AGGSPLIT_SERIALIZE(fc_aggstate->aggsplit))
			{
				/* 仅在未运行 finalfn 时序列化有效 */
				Assert(DO_AGGSPLIT_SKIPFINAL(fc_aggstate->aggsplit));

				if (!OidIsValid(fc_aggform->aggserialfn))
					elog(ERROR, "serialfunc not provided for serialization aggregation");
				fc_serialfn_oid = fc_aggform->aggserialfn;
			}

			/* 反序列化函数同样适用 */
			if (DO_AGGSPLIT_DESERIALIZE(fc_aggstate->aggsplit))
			{
				/* 仅在合并状态时反序列化有效 */
				Assert(DO_AGGSPLIT_COMBINE(fc_aggstate->aggsplit));

				if (!OidIsValid(fc_aggform->aggdeserialfn))
					elog(ERROR, "deserialfunc not provided for deserialization aggregation");
				fc_deserialfn_oid = fc_aggform->aggdeserialfn;
			}
		}

		/* 检查聚合所有者是否有权限调用组件函数 */
		{
			HeapTuple	fc_procTuple;

			fc_procTuple = SearchSysCache1(PROCOID,
										ObjectIdGetDatum(fc_aggref->aggfnoid));
			if (!HeapTupleIsValid(fc_procTuple))
				elog(ERROR, "cache lookup failed for function %u",
					 fc_aggref->aggfnoid);
			fc_aggOwner = ((Form_pg_proc) GETSTRUCT(fc_procTuple))->proowner;
			ReleaseSysCache(fc_procTuple);

			if (OidIsValid(fc_finalfn_oid))
			{
				fc_aclresult = pg_proc_aclcheck(fc_finalfn_oid, fc_aggOwner,
											 ACL_EXECUTE);
				if (fc_aclresult != ACLCHECK_OK)
					aclcheck_error(fc_aclresult, OBJECT_FUNCTION,
								   get_func_name(fc_finalfn_oid));
				InvokeFunctionExecuteHook(fc_finalfn_oid);
			}
			if (OidIsValid(fc_serialfn_oid))
			{
				fc_aclresult = pg_proc_aclcheck(fc_serialfn_oid, fc_aggOwner,
											 ACL_EXECUTE);
				if (fc_aclresult != ACLCHECK_OK)
					aclcheck_error(fc_aclresult, OBJECT_FUNCTION,
								   get_func_name(fc_serialfn_oid));
				InvokeFunctionExecuteHook(fc_serialfn_oid);
			}
			if (OidIsValid(fc_deserialfn_oid))
			{
				fc_aclresult = pg_proc_aclcheck(fc_deserialfn_oid, fc_aggOwner,
											 ACL_EXECUTE);
				if (fc_aclresult != ACLCHECK_OK)
					aclcheck_error(fc_aclresult, OBJECT_FUNCTION,
								   get_func_name(fc_deserialfn_oid));
				InvokeFunctionExecuteHook(fc_deserialfn_oid);
			}
		}

		/*
		 * 获取（名义）聚合输入的实际数据类型。这些
		 * 可能与聚合声明的输入类型不同，当聚合
		 * 接受 ANY 或多态类型时。
		 */
		fc_numAggTransFnArgs = get_aggregate_argtypes(fc_aggref,
												   fc_aggTransFnInputTypes);

		/* 计算“直接”参数，如果有的话 */
		fc_numDirectArgs = list_length(fc_aggref->aggdirectargs);

		/* 检测要传递给 finalfn 的参数数量 */
		if (fc_aggform->aggfinalextra)
			fc_peragg->numFinalArgs = fc_numAggTransFnArgs + 1;
		else
			fc_peragg->numFinalArgs = fc_numDirectArgs + 1;

		/* 初始化任何直接参数表达式 */
		fc_peragg->aggdirectargs = ExecInitExprList(fc_aggref->aggdirectargs,
												 (PlanState *) fc_aggstate);

		/*
		 * 使用实际参数和结果类型构建最终函数的表达式树，
		 * 如果存在且需要的话。
		 */
		if (OidIsValid(fc_finalfn_oid))
		{
			build_aggregate_finalfn_expr(fc_aggTransFnInputTypes,
										 fc_peragg->numFinalArgs,
										 fc_aggtranstype,
										 fc_aggref->aggtype,
										 fc_aggref->inputcollid,
										 fc_finalfn_oid,
										 &fc_finalfnexpr);
			fmgr_info(fc_finalfn_oid, &fc_peragg->finalfn);
			fmgr_info_set_expr((Node *) fc_finalfnexpr, &fc_peragg->finalfn);
		}

		/* 获取输出值数据类型的信息 */
		get_typlenbyval(fc_aggref->aggtype,
						&fc_peragg->resulttypeLen,
						&fc_peragg->resulttypeByVal);

		/*
		 * 构建调用转换函数的工作状态，如果我们还没有完成的话。
		 */
		fc_pertrans = &fc_pertransstates[fc_aggref->aggtransno];
		if (fc_pertrans->aggref == NULL)
		{
			Datum		fc_textInitVal;
			Datum		fc_initValue;
			bool		fc_initValueIsNull;
			Oid			fc_transfn_oid;

			/*
			 * 如果这个聚合执行状态组合，则我们将使用组合函数，而不是使用转换函数。
			 */
			if (DO_AGGSPLIT_COMBINE(fc_aggstate->aggsplit))
			{
				fc_transfn_oid = fc_aggform->aggcombinefn;

				/* 如果未设置，那么规划程序搞砸了 */
				if (!OidIsValid(fc_transfn_oid))
					elog(ERROR, "combinefn not set for aggregate function");
			}
			else
				fc_transfn_oid = fc_aggform->aggtransfn;

			fc_aclresult = pg_proc_aclcheck(fc_transfn_oid, fc_aggOwner, ACL_EXECUTE);
			if (fc_aclresult != ACLCHECK_OK)
				aclcheck_error(fc_aclresult, OBJECT_FUNCTION,
							   get_func_name(fc_transfn_oid));
			InvokeFunctionExecuteHook(fc_transfn_oid);

			/*
			 * initval 可能为 null，因此不要尝试将其作为结构字段访问。必须通过 SysCacheGetAttr 以困难的方式执行此操作。
			 */
			fc_textInitVal = SysCacheGetAttr(AGGFNOID, fc_aggTuple,
										  Anum_pg_aggregate_agginitval,
										  &fc_initValueIsNull);
			if (fc_initValueIsNull)
				fc_initValue = (Datum) 0;
			else
				fc_initValue = fc_GetAggInitVal(fc_textInitVal, fc_aggtranstype);

			if (DO_AGGSPLIT_COMBINE(fc_aggstate->aggsplit))
			{
				Oid			fc_combineFnInputTypes[] = {fc_aggtranstype,
				fc_aggtranstype};

				/*
				 * 组合时只有一个输入，即待组合的过渡值。过渡值在这里不算。
				 */
				fc_pertrans->numTransInputs = 1;

				/* aggcombinefn 始终有两个 aggtranstype 的参数 */
				fc_build_pertrans_for_aggref(fc_pertrans, fc_aggstate, fc_estate,
										  fc_aggref, fc_transfn_oid, fc_aggtranstype,
										  fc_serialfn_oid, fc_deserialfn_oid,
										  fc_initValue, fc_initValueIsNull,
										  fc_combineFnInputTypes, 2);

				/*
				 * 确保用于组合 INTERNAL 状态的组合函数不是严格的。这应该在创建聚合时检查过，但严格属性可能在那之后被更改。
				 */
				if (fc_pertrans->transfn.fn_strict && fc_aggtranstype == INTERNALOID)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
							 errmsg("combine function with transition type %s must not be declared STRICT",
									format_type_be(fc_aggtranstype))));
			}
			else
			{
				/* 检测要传递给 transfn 的参数数量 */
				if (AGGKIND_IS_ORDERED_SET(fc_aggref->aggkind))
					fc_pertrans->numTransInputs = list_length(fc_aggref->args);
				else
					fc_pertrans->numTransInputs = fc_numAggTransFnArgs;

				fc_build_pertrans_for_aggref(fc_pertrans, fc_aggstate, fc_estate,
										  fc_aggref, fc_transfn_oid, fc_aggtranstype,
										  fc_serialfn_oid, fc_deserialfn_oid,
										  fc_initValue, fc_initValueIsNull,
										  fc_aggTransFnInputTypes,
										  fc_numAggTransFnArgs);

				/*
				 * 如果 transfn 是严格的并且 initval 为 NULL，请确保输入类型和 transtype 是相同的（或者至少是二进制兼容的），以便可以将第一个聚合输入值作为初始 transValue。这应该在聚合定义时检查过，但我们必须再次检查，以防 transfn 的严格性属性已被更改。
				 */
				if (fc_pertrans->transfn.fn_strict && fc_pertrans->initValueIsNull)
				{
					if (fc_numAggTransFnArgs <= fc_numDirectArgs ||
						!IsBinaryCoercible(fc_aggTransFnInputTypes[fc_numDirectArgs],
										   fc_aggtranstype))
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
								 errmsg("aggregate %u needs to have compatible input type and transition type",
										fc_aggref->aggfnoid)));
				}
			}
		}
		else
			fc_pertrans->aggshared = true;
		ReleaseSysCache(fc_aggTuple);
	}

	/*
	 * 更新 aggstate->numaggs 为找到的唯一聚合的数量。
	 * 同时设置 numstates 为找到的唯一过渡状态的数量。
	 */
	fc_aggstate->numaggs = fc_numaggs;
	fc_aggstate->numtrans = fc_numtrans;

	/*
	 * 最后，检查在处理聚合参数的表达式时，是否有更多的聚合添加到了节点上（不仅包括上面立即处理的常规参数和 FILTER 表达式，还包括我们可能之前处理过的任何直接参数）。如果是这样，我们有嵌套的聚合函数，这是在语义上没有意义的，因此要抱怨。（这应该在解析器中捕获，因此我们不需要费心编写有用的错误信息；但我们在这里仍然进行防御，以确保万无一失。）
	 */
	if (fc_numaggrefs != list_length(fc_aggstate->aggs))
		ereport(ERROR,
				(errcode(ERRCODE_GROUPING_ERROR),
				 errmsg("aggregate function calls cannot be nested")));

	/*
	 * 一次构建执行所有过渡工作的表达式。我们为每个阶段构建不同的表达式，因为不同阶段的过渡函数调用次数可能不同。请注意，这将同时适用于过渡和组合函数（尽管在后者情况下仅有一个阶段）。
	 */
	for (fc_phaseidx = 0; fc_phaseidx < fc_aggstate->numphases; fc_phaseidx++)
	{
		AggStatePerPhase fc_phase = &fc_aggstate->phases[fc_phaseidx];
		bool		fc_dohash = false;
		bool		fc_dosort = false;

		/* 阶段 0 不一定存在 */
		if (!fc_phase->aggnode)
			continue;

		if (fc_aggstate->aggstrategy == AGG_MIXED && fc_phaseidx == 1)
		{
			/*
			 * 阶段一，且仅有阶段一，在混合聚合中同时执行排序和聚合。
			 */
			fc_dohash = true;
			fc_dosort = true;
		}
		else if (fc_aggstate->aggstrategy == AGG_MIXED && fc_phaseidx == 0)
		{
			/*
			 * 对于 AGG_MIXED 阶段 0，无需计算转换函数 - 哈希表的内容将在阶段 1 中计算。
			 */
			continue;
		}
		else if (fc_phase->aggstrategy == AGG_PLAIN ||
				 fc_phase->aggstrategy == AGG_SORTED)
		{
			fc_dohash = false;
			fc_dosort = true;
		}
		else if (fc_phase->aggstrategy == AGG_HASHED)
		{
			fc_dohash = true;
			fc_dosort = false;
		}
		else
			Assert(false);

		fc_phase->evaltrans = ExecBuildAggTrans(fc_aggstate, fc_phase, fc_dosort, fc_dohash,
											 false);

		/* 为外部槽缓存已编译的表达式，无需 NULL 检查 */
		fc_phase->evaltrans_cache[0][0] = fc_phase->evaltrans;
	}

	return fc_aggstate;
}

/*
 * 构建计算聚合状态值所需的状态。
 *
 * 这将初始化 'pertrans' 中的所有字段。'aggref' 是要初始化状态的聚合。
 * 'transfn_oid'、'aggtranstype' 和其余参数可以从 'aggref' 中计算，但调用者已经计算过，因此可以传递它们。
 *
 * 'transfn_oid' 可以是 aggtransfn 或 aggcombinefn 的 Oid。
 */
static void fc_build_pertrans_for_aggref(AggStatePerTrans fc_pertrans,
						  AggState *fc_aggstate, EState *fc_estate,
						  Aggref *fc_aggref,
						  Oid fc_transfn_oid, Oid fc_aggtranstype,
						  Oid fc_aggserialfn, Oid fc_aggdeserialfn,
						  Datum fc_initValue, bool fc_initValueIsNull,
						  Oid *fc_inputTypes, int fc_numArguments)
{
	int			fc_numGroupingSets = Max(fc_aggstate->maxsets, 1);
	Expr	   *fc_transfnexpr;
	int			fc_numTransArgs;
	Expr	   *fc_serialfnexpr = NULL;
	Expr	   *fc_deserialfnexpr = NULL;
	ListCell   *fc_lc;
	int			fc_numInputs;
	int			fc_numDirectArgs;
	List	   *fc_sortlist;
	int			fc_numSortCols;
	int			fc_numDistinctCols;
	int			fc_i;

	/* 开始填充 pertrans 数据 */
	fc_pertrans->aggref = fc_aggref;
	fc_pertrans->aggshared = false;
	fc_pertrans->aggCollation = fc_aggref->inputcollid;
	fc_pertrans->transfn_oid = fc_transfn_oid;
	fc_pertrans->serialfn_oid = fc_aggserialfn;
	fc_pertrans->deserialfn_oid = fc_aggdeserialfn;
	fc_pertrans->initValue = fc_initValue;
	fc_pertrans->initValueIsNull = fc_initValueIsNull;

	/* 计算“直接”参数，如果有的话 */
	fc_numDirectArgs = list_length(fc_aggref->aggdirectargs);

	/* 计算聚合输入列的数量 */
	fc_pertrans->numInputs = fc_numInputs = list_length(fc_aggref->args);

	fc_pertrans->aggtranstype = fc_aggtranstype;

	/* 考虑当前的过渡状态 */
	fc_numTransArgs = fc_pertrans->numTransInputs + 1;

	/*
	 * 设置调用 transfn 的基础设施。请注意，这里不需要 invtrans。
	 */
	build_aggregate_transfn_expr(fc_inputTypes,
								 fc_numArguments,
								 fc_numDirectArgs,
								 fc_aggref->aggvariadic,
								 fc_aggtranstype,
								 fc_aggref->inputcollid,
								 fc_transfn_oid,
								 InvalidOid,
								 &fc_transfnexpr,
								 NULL);

	fmgr_info(fc_transfn_oid, &fc_pertrans->transfn);
	fmgr_info_set_expr((Node *) fc_transfnexpr, &fc_pertrans->transfn);

	fc_pertrans->transfn_fcinfo =
		(FunctionCallInfo) palloc(SizeForFunctionCallInfo(fc_numTransArgs));
	InitFunctionCallInfoData(*fc_pertrans->transfn_fcinfo,
							 &fc_pertrans->transfn,
							 fc_numTransArgs,
							 fc_pertrans->aggCollation,
							 (void *) fc_aggstate, NULL);

	/* 获取有关状态值数据类型的信息 */
	get_typlenbyval(fc_aggtranstype,
					&fc_pertrans->transtypeLen,
					&fc_pertrans->transtypeByVal);

	if (OidIsValid(fc_aggserialfn))
	{
		build_aggregate_serialfn_expr(fc_aggserialfn,
									  &fc_serialfnexpr);
		fmgr_info(fc_aggserialfn, &fc_pertrans->serialfn);
		fmgr_info_set_expr((Node *) fc_serialfnexpr, &fc_pertrans->serialfn);

		fc_pertrans->serialfn_fcinfo =
			(FunctionCallInfo) palloc(SizeForFunctionCallInfo(1));
		InitFunctionCallInfoData(*fc_pertrans->serialfn_fcinfo,
								 &fc_pertrans->serialfn,
								 1,
								 InvalidOid,
								 (void *) fc_aggstate, NULL);
	}

	if (OidIsValid(fc_aggdeserialfn))
	{
		build_aggregate_deserialfn_expr(fc_aggdeserialfn,
										&fc_deserialfnexpr);
		fmgr_info(fc_aggdeserialfn, &fc_pertrans->deserialfn);
		fmgr_info_set_expr((Node *) fc_deserialfnexpr, &fc_pertrans->deserialfn);

		fc_pertrans->deserialfn_fcinfo =
			(FunctionCallInfo) palloc(SizeForFunctionCallInfo(2));
		InitFunctionCallInfoData(*fc_pertrans->deserialfn_fcinfo,
								 &fc_pertrans->deserialfn,
								 2,
								 InvalidOid,
								 (void *) fc_aggstate, NULL);
	}

	/*
	 * 如果我们是为了一个普通聚合进行 DISTINCT 或 ORDER BY，那么我们有一组 SortGroupClause 节点；从中提取数据并将其放入数组中。然而，对于有序集合聚合，我们忽略 ORDER BY；聚合的 transfn 和 finalfn 负责处理。
	 *
	 * 请注意，通过构造，如果存在 DISTINCT 子句，则 ORDER BY 子句是其前缀（请参阅 transformDistinctClause）。
	 */
	if (AGGKIND_IS_ORDERED_SET(fc_aggref->aggkind))
	{
		fc_sortlist = NIL;
		fc_numSortCols = fc_numDistinctCols = 0;
	}
	else if (fc_aggref->aggdistinct)
	{
		fc_sortlist = fc_aggref->aggdistinct;
		fc_numSortCols = fc_numDistinctCols = list_length(fc_sortlist);
		Assert(fc_numSortCols >= list_length(fc_aggref->aggorder));
	}
	else
	{
		fc_sortlist = fc_aggref->aggorder;
		fc_numSortCols = list_length(fc_sortlist);
		fc_numDistinctCols = 0;
	}

	fc_pertrans->numSortCols = fc_numSortCols;
	fc_pertrans->numDistinctCols = fc_numDistinctCols;

	/*
	 * 如果我们需要进行排序或过滤，创建一个与聚合输入（包括排序表达式）相对应的 tupledesc 和槽。
	 */
	if (fc_numSortCols > 0 || fc_aggref->aggfilter)
	{
		fc_pertrans->sortdesc = ExecTypeFromTL(fc_aggref->args);
		fc_pertrans->sortslot =
			ExecInitExtraTupleSlot(fc_estate, fc_pertrans->sortdesc,
								   &TTSOpsMinimalTuple);
	}

	if (fc_numSortCols > 0)
	{
		/*
		 * 在 HASHED 情况下我们不实现 DISTINCT 或 ORDER BY 聚合（尚未）。
		 */
		Assert(fc_aggstate->aggstrategy != AGG_HASHED && fc_aggstate->aggstrategy != AGG_MIXED);

		/* 不支持带部分聚合的 ORDER BY 聚合 */
		Assert(!DO_AGGSPLIT_COMBINE(fc_aggstate->aggsplit));

		/* 如果我们只有一个输入，我们需要它的长度/按值信息。 */
		if (fc_numInputs == 1)
		{
			get_typlenbyval(fc_inputTypes[fc_numDirectArgs],
							&fc_pertrans->inputtypeLen,
							&fc_pertrans->inputtypeByVal);
		}
		else if (fc_numDistinctCols > 0)
		{
			/* 我们将需要一个额外的槽来存储前一个值 */
			fc_pertrans->uniqslot =
				ExecInitExtraTupleSlot(fc_estate, fc_pertrans->sortdesc,
									   &TTSOpsMinimalTuple);
		}

		/* 提取排序信息以供后用 */
		fc_pertrans->sortColIdx =
			(AttrNumber *) palloc(fc_numSortCols * sizeof(AttrNumber));
		fc_pertrans->sortOperators =
			(Oid *) palloc(fc_numSortCols * sizeof(Oid));
		fc_pertrans->sortCollations =
			(Oid *) palloc(fc_numSortCols * sizeof(Oid));
		fc_pertrans->sortNullsFirst =
			(bool *) palloc(fc_numSortCols * sizeof(bool));

		fc_i = 0;
		foreach(fc_lc, fc_sortlist)
		{
			SortGroupClause *fc_sortcl = (SortGroupClause *) lfirst(fc_lc);
			TargetEntry *fc_tle = get_sortgroupclause_tle(fc_sortcl, fc_aggref->args);

			/* 解析器应该确保这一点 */
			Assert(OidIsValid(fc_sortcl->sortop));

			fc_pertrans->sortColIdx[fc_i] = fc_tle->resno;
			fc_pertrans->sortOperators[fc_i] = fc_sortcl->sortop;
			fc_pertrans->sortCollations[fc_i] = exprCollation((Node *) fc_tle->expr);
			fc_pertrans->sortNullsFirst[fc_i] = fc_sortcl->nulls_first;
			fc_i++;
		}
		Assert(fc_i == fc_numSortCols);
	}

	if (fc_aggref->aggdistinct)
	{
		Oid		   *fc_ops;

		Assert(fc_numArguments > 0);
		Assert(list_length(fc_aggref->aggdistinct) == fc_numDistinctCols);

		fc_ops = palloc(fc_numDistinctCols * sizeof(Oid));

		fc_i = 0;
		foreach(fc_lc, fc_aggref->aggdistinct)
			fc_ops[fc_i++] = ((SortGroupClause *) lfirst(fc_lc))->eqop;

		/*查找 / 构建必要的比较器*/
		if (fc_numDistinctCols == 1)
			fmgr_info(get_opcode(fc_ops[0]), &fc_pertrans->equalfnOne);
		else
			fc_pertrans->equalfnMulti =
				execTuplesMatchPrepare(fc_pertrans->sortdesc,
									   fc_numDistinctCols,
									   fc_pertrans->sortColIdx,
									   fc_ops,
									   fc_pertrans->sortCollations,
									   &fc_aggstate->ss.ps);
		pfree(fc_ops);
	}

	fc_pertrans->sortstates = (Tuplesortstate **)
		palloc0(sizeof(Tuplesortstate *) * fc_numGroupingSets);
}


static Datum fc_GetAggInitVal(Datum fc_textInitVal, Oid fc_transtype)
{
	Oid			fc_typinput,
				fc_typioparam;
	char	   *fc_strInitVal;
	Datum		fc_initVal;

	getTypeInputInfo(fc_transtype, &fc_typinput, &fc_typioparam);
	fc_strInitVal = TextDatumGetCString(fc_textInitVal);
	fc_initVal = OidInputFunctionCall(fc_typinput, fc_strInitVal,
								   fc_typioparam, -1);
	pfree(fc_strInitVal);
	return fc_initVal;
}

void ExecEndAgg(AggState *fc_node)
{
	PlanState  *fc_outerPlan;
	int			fc_transno;
	int			fc_numGroupingSets = Max(fc_node->maxsets, 1);
	int			fc_setno;

	/*
	 * 当结束一个并行工作者时，将工作者收集到的统计信息
	 * 复制回共享内存，以便主进程可以在 EXPLAIN ANALYZE 中报告。
	 */
	if (fc_node->shared_info && IsParallelWorker())
	{
		AggregateInstrumentation *fc_si;

		Assert(ParallelWorkerNumber <= fc_node->shared_info->num_workers);
		fc_si = &fc_node->shared_info->sinstrument[ParallelWorkerNumber];
		fc_si->hash_batches_used = fc_node->hash_batches_used;
		fc_si->hash_disk_used = fc_node->hash_disk_used;
		fc_si->hash_mem_peak = fc_node->hash_mem_peak;
	}

	/* 确保我们关闭了任何打开的元组排序 */

	if (fc_node->sort_in)
		tuplesort_end(fc_node->sort_in);
	if (fc_node->sort_out)
		tuplesort_end(fc_node->sort_out);

	fc_hashagg_reset_spill_state(fc_node);

	if (fc_node->hash_metacxt != NULL)
	{
		MemoryContextDelete(fc_node->hash_metacxt);
		fc_node->hash_metacxt = NULL;
	}

	for (fc_transno = 0; fc_transno < fc_node->numtrans; fc_transno++)
	{
		AggStatePerTrans fc_pertrans = &fc_node->pertrans[fc_transno];

		for (fc_setno = 0; fc_setno < fc_numGroupingSets; fc_setno++)
		{
			if (fc_pertrans->sortstates[fc_setno])
				tuplesort_end(fc_pertrans->sortstates[fc_setno]);
		}
	}

	/* 并确保调用了任何聚合关闭回调 */
	for (fc_setno = 0; fc_setno < fc_numGroupingSets; fc_setno++)
		ReScanExprContext(fc_node->aggcontexts[fc_setno]);
	if (fc_node->hashcontext)
		ReScanExprContext(fc_node->hashcontext);

	/*
	 * 我们这里实际上没有释放任何 ExprContexts（请参阅
	 * ExecFreeExprContext 中的注释），仅仅从计划节点中解除链接输出一个即可。
	 */
	ExecFreeExprContext(&fc_node->ss.ps);

	/* 清理元组表 */
	ExecClearTuple(fc_node->ss.ss_ScanTupleSlot);

	fc_outerPlan = outerPlanState(fc_node);
	ExecEndNode(fc_outerPlan);
}

void ExecReScanAgg(AggState *fc_node)
{
	ExprContext *fc_econtext = fc_node->ss.ps.ps_ExprContext;
	PlanState  *fc_outerPlan = outerPlanState(fc_node);
	Agg		   *fc_aggnode = (Agg *) fc_node->ss.ps.plan;
	int			fc_transno;
	int			fc_numGroupingSets = Max(fc_node->maxsets, 1);
	int			fc_setno;

	fc_node->agg_done = false;

	if (fc_node->aggstrategy == AGG_HASHED)
	{
		/*
		 * 在哈希情况下，如果我们还没有构建哈希表，那么我们
		 * 只需返回；什么也没做，所以无须撤销。如果子节点的
		 * chgParam 不是 NULL，那么它将由 ExecProcNode 重新扫描，
		 * 否则完全没有理由重新扫描它。
		 */
		if (!fc_node->table_filled)
			return;

		/*
		 * 如果我们确实有哈希表，并且它从未溢出，并且子计划
		 * 没有任何参数更改，并且我们自己的参数更改没有影响聚合函数的输入表达式，
		 * 那么我们只需重新扫描现有的哈希表；无须重新构建它。
		 */
		if (fc_outerPlan->chgParam == NULL && !fc_node->hash_ever_spilled &&
			!bms_overlap(fc_node->ss.ps.chgParam, fc_aggnode->aggParams))
		{
			ResetTupleHashIterator(fc_node->perhash[0].hashtable,
								   &fc_node->perhash[0].hashiter);
			fc_select_current_set(fc_node, 0, true);
			return;
		}
	}

	/* 确保我们关闭了任何打开的元组排序 */
	for (fc_transno = 0; fc_transno < fc_node->numtrans; fc_transno++)
	{
		for (fc_setno = 0; fc_setno < fc_numGroupingSets; fc_setno++)
		{
			AggStatePerTrans fc_pertrans = &fc_node->pertrans[fc_transno];

			if (fc_pertrans->sortstates[fc_setno])
			{
				tuplesort_end(fc_pertrans->sortstates[fc_setno]);
				fc_pertrans->sortstates[fc_setno] = NULL;
			}
		}
	}

	/*
	 * 我们这里不需要重新扫描输出元组上下文；
	 * ExecReScan 已经做到了。但我们确实需要重置我们的每组上下文，
	 * 其中可能存储了 transvalues。（我们使用重新扫描
	 * 而不是仅仅重置，因为 transfns 可能已注册需要现在运行的回调。）
	 * 对于 AGG_HASHED 情况，请参阅下面。
	 */

	for (fc_setno = 0; fc_setno < fc_numGroupingSets; fc_setno++)
	{
		ReScanExprContext(fc_node->aggcontexts[fc_setno]);
	}

	/* 如果我们做了副本，则释放组的第一个元组 */
	if (fc_node->grp_firstTuple != NULL)
	{
		heap_freetuple(fc_node->grp_firstTuple);
		fc_node->grp_firstTuple = NULL;
	}
	ExecClearTuple(fc_node->ss.ss_ScanTupleSlot);

	/* 忘记当前聚合值 */
	MemSet(fc_econtext->ecxt_aggvalues, 0, sizeof(Datum) * fc_node->numaggs);
	MemSet(fc_econtext->ecxt_aggnulls, 0, sizeof(bool) * fc_node->numaggs);

	/*
	 * 在 AGG_HASHED/MIXED 的情况下，哈希表在哈希上下文的
	 * 子上下文中分配。这曾是一个问题，但现在，重置上下文
	 * 会自动删除子上下文。
	 */
	if (fc_node->aggstrategy == AGG_HASHED || fc_node->aggstrategy == AGG_MIXED)
	{
		fc_hashagg_reset_spill_state(fc_node);

		fc_node->hash_ever_spilled = false;
		fc_node->hash_spill_mode = false;
		fc_node->hash_ngroups_current = 0;

		ReScanExprContext(fc_node->hashcontext);
		/* 重新构建一个空哈希表 */
		fc_build_hash_tables(fc_node);
		fc_node->table_filled = false;
		/* 当表被填充时，迭代器将被重置 */

		fc_hashagg_recompile_expressions(fc_node, false, false);
	}

	if (fc_node->aggstrategy != AGG_HASHED)
	{
		/*
		 * 重置每组状态（特别是，将 transvalues 标记为 null）
		 */
		for (fc_setno = 0; fc_setno < fc_numGroupingSets; fc_setno++)
		{
			MemSet(fc_node->pergroups[fc_setno], 0,
				   sizeof(AggStatePerGroupData) * fc_node->numaggs);
		}

		/* 重置为阶段 1 */
		fc_initialize_phase(fc_node, 1);

		fc_node->input_done = false;
		fc_node->projected_set = -1;
	}

	if (fc_outerPlan->chgParam == NULL)
		ExecReScan(fc_outerPlan);
}


/***********************************************************************
 * 提供给聚合函数的 API
 ***********************************************************************/


/*
 * AggCheckCallContext - 测试 SQL 函数是否作为聚合进行调用
 *
 * 聚合的过渡和/或最终函数可能希望验证
 * 它们是否作为聚合被调用，而不是作为普通的 SQL
 * 函数。它们应该使用此函数来进行验证。返回值
 * 如果作为聚合被调用则为非零，否则为零。（特定的
 * 非零值是 AGG_CONTEXT_AGGREGATE 或 AGG_CONTEXT_WINDOW，但更多
 * 值在未来可能会出现。）
 *
 * 如果 aggcontext 不是 NULL，函数还在 *aggcontext 中存储
 * 聚合过渡值存储在的内存上下文的标识。注意，同一聚合调用位置
 * (flinfo) 可能在不同上下文中针对不同的过渡值进行交错调用，
 * 因此在 fn_extra 下缓存 aggcontext 是不合适的。不过，
 * 将其缓存到 transvalue 本身（对于内部类型的 transvalues）
 * 是合适的。
 */
int AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *fc_aggcontext)
{
	if (fcinfo->context && IsA(fcinfo->context, AggState))
	{
		if (fc_aggcontext)
		{
			AggState   *fc_aggstate = ((AggState *) fcinfo->context);
			ExprContext *fc_cxt = fc_aggstate->curaggcontext;

			*fc_aggcontext = fc_cxt->ecxt_per_tuple_memory;
		}
		return AGG_CONTEXT_AGGREGATE;
	}
	if (fcinfo->context && IsA(fcinfo->context, WindowAggState))
	{
		if (fc_aggcontext)
			*fc_aggcontext = ((WindowAggState *) fcinfo->context)->curaggcontext;
		return AGG_CONTEXT_WINDOW;
	}

	/* 这只是为了防止“未初始化变量”警告 */
	if (fc_aggcontext)
		*fc_aggcontext = NULL;
	return 0;
}

/*
 * AggGetAggref - 允许聚合支持函数获取其 Aggref
 *
 * 如果函数作为聚合支持函数被调用，
 * 返回聚合调用的 Aggref 节点。否则，返回 NULL。
 *
 * 共享相同输入和过渡函数的聚合可以合并为
 * 单个过渡计算。如果过渡函数调用 AggGetAggref，
 * 它将获得正在执行的一些 Aggrefs。因此，它必须不
 * 注意与最终函数相关的 Aggref 字段，因为那些是不确定的。
 * 但是，如果最终函数调用 AggGetAggref，它将获得精确结果。
 *
 * 注意，如果一个聚合被用作窗口函数，这将
 * 返回 NULL。我们可以提供一个类似的函数在这种情况下
 * 返回相关的 WindowFunc 节点，但目前还不需要。
 */
Aggref *
AggGetAggref(FunctionCallInfo fcinfo)
{
	if (fcinfo->context && IsA(fcinfo->context, AggState))
	{
		AggState   *fc_aggstate = (AggState *) fcinfo->context;
		AggStatePerAgg fc_curperagg;
		AggStatePerTrans fc_curpertrans;

		/* 检查 curperagg（在最终函数中有效） */
		fc_curperagg = fc_aggstate->curperagg;

		if (fc_curperagg)
			return fc_curperagg->aggref;

		/* 检查 curpertrans（在过渡函数中有效） */
		fc_curpertrans = fc_aggstate->curpertrans;

		if (fc_curpertrans)
			return fc_curpertrans->aggref;
	}
	return NULL;
}

/*
 * AggGetTempMemoryContext - 获取聚合的短期内存上下文
 *
 * 这在聚合最终函数中很有用；返回的上下文是最终函数可以安全地按需重置的。对于过渡函数，这没有用，因为返回的上下文可能（我们不承诺）与调用它们所用的上下文相同。
 *
 * 如上所述，目前这在作为窗口函数调用的聚合中并不有用。
 */
MemoryContext AggGetTempMemoryContext(FunctionCallInfo fcinfo)
{
	if (fcinfo->context && IsA(fcinfo->context, AggState))
	{
		AggState   *fc_aggstate = (AggState *) fcinfo->context;

		return fc_aggstate->tmpcontext->ecxt_per_tuple_memory;
	}
	return NULL;
}

/*
 * AggStateIsShared - 了解过渡状态是否共享
 *
 * 如果函数作为聚合支持函数被调用，则如果聚合的过渡状态在多个聚合之间共享，则返回true；否则返回false。
 *
 * 如果没有被作为聚合支持函数调用，则返回true。这是一个保守的答案，即“没有，你最好不要对你的输入进行随意操作”。特别是，如果聚合作为窗口函数使用，将返回true，这是一种改变过渡状态的坏主意的场景。我们可能希望在未来改进窗口案例的行为。
 */
bool AggStateIsShared(FunctionCallInfo fcinfo)
{
	if (fcinfo->context && IsA(fcinfo->context, AggState))
	{
		AggState   *fc_aggstate = (AggState *) fcinfo->context;
		AggStatePerAgg fc_curperagg;
		AggStatePerTrans fc_curpertrans;

		/* 检查 curperagg（在最终函数中有效） */
		fc_curperagg = fc_aggstate->curperagg;

		if (fc_curperagg)
			return fc_aggstate->pertrans[fc_curperagg->transno].aggshared;

		/* 检查 curpertrans（在过渡函数中有效） */
		fc_curpertrans = fc_aggstate->curpertrans;

		if (fc_curpertrans)
			return fc_curpertrans->aggshared;
	}
	return true;
}

/*
 * AggRegisterCallback - 为聚合注册清理回调
 *
 * 这对于聚合注册关机回调很有用，这将确保释放非内存资源。该回调将在相关的aggcontext（由AggCheckCallContext返回）重置之前发生，无论是组之间还是由于重新扫描查询。该回调将在错误路径上不会被调用。典型的用例是释放存储在aggcontext中的tuplestores或tuplesorts，或者释放由聚合函数创建的槽所持有的固定对象。（该回调不会在最终函数的结果不再需要之后被调用，因此最终函数返回的数据可以被回调安全地释放。）
 *
 * 如上所述，目前这在作为窗口函数调用的聚合中并不有用。
 */
void AggRegisterCallback(FunctionCallInfo fcinfo,
					ExprContextCallbackFunction fc_func,
					Datum fc_arg)
{
	if (fcinfo->context && IsA(fcinfo->context, AggState))
	{
		AggState   *fc_aggstate = (AggState *) fcinfo->context;
		ExprContext *fc_cxt = fc_aggstate->curaggcontext;

		RegisterExprContextCallback(fc_cxt, fc_func, fc_arg);

		return;
	}
	elog(ERROR, "aggregate function cannot register a callback in this context");
}


/* ----------------------------------------------------------------
 *						并行查询支持
 * ----------------------------------------------------------------
 */

 /* ----------------------------------------------------------------
  *		ExecAggEstimate
  *
  *		估算传播聚合统计所需的空间。
  * ----------------------------------------------------------------
  */
void ExecAggEstimate(AggState *fc_node, ParallelContext *fc_pcxt)
{
	Size		fc_size;

	/* 如果没有工具监控或没有工作程序，则不需要此 */
	if (!fc_node->ss.ps.instrument || fc_pcxt->nworkers == 0)
		return;

	fc_size = mul_size(fc_pcxt->nworkers, sizeof(AggregateInstrumentation));
	fc_size = add_size(fc_size, offsetof(SharedAggInfo, sinstrument));
	shm_toc_estimate_chunk(&fc_pcxt->estimator, fc_size);
	shm_toc_estimate_keys(&fc_pcxt->estimator, 1);
}

/* ----------------------------------------------------------------
 *		ExecAggInitializeDSM
 *
 *		初始化聚合统计的DSM空间。
 * ----------------------------------------------------------------
 */
void ExecAggInitializeDSM(AggState *fc_node, ParallelContext *fc_pcxt)
{
	Size		fc_size;

	/* 如果没有工具监控或没有工作程序，则不需要此 */
	if (!fc_node->ss.ps.instrument || fc_pcxt->nworkers == 0)
		return;

	fc_size = offsetof(SharedAggInfo, sinstrument)
		+ fc_pcxt->nworkers * sizeof(AggregateInstrumentation);
	fc_node->shared_info = shm_toc_allocate(fc_pcxt->toc, fc_size);
	
/* 确保任何未填充的槽将包含零 */
	memset(fc_node->shared_info, 0, fc_size);
	fc_node->shared_info->num_workers = fc_pcxt->nworkers;
	shm_toc_insert(fc_pcxt->toc, fc_node->ss.ps.plan->plan_node_id,
				   fc_node->shared_info);
}


/* ----------------------------------------------------------------
 *		ExecAggInitializeWorker
 *
 *		将工作者附加到DSM空间以获取聚合统计信息。
 * ----------------------------------------------------------------
 */
void ExecAggInitializeWorker(AggState *fc_node, ParallelWorkerContext *fc_pwcxt)
{
	fc_node->shared_info =
		shm_toc_lookup(fc_pwcxt->toc, fc_node->ss.ps.plan->plan_node_id, true);
}

/* ----------------------------------------------------------------
 *		ExecAggRetrieveInstrumentation
 *
 *		将聚合统计信息从DSM传输到私有内存。
 * ----------------------------------------------------------------
 */
void ExecAggRetrieveInstrumentation(AggState *fc_node)
{
	Size		fc_size;
	SharedAggInfo *fc_si;

	if (fc_node->shared_info == NULL)
		return;

	fc_size = offsetof(SharedAggInfo, sinstrument)
		+ fc_node->shared_info->num_workers * sizeof(AggregateInstrumentation);
	fc_si = palloc(fc_size);
	memcpy(fc_si, fc_node->shared_info, fc_size);
	fc_node->shared_info = fc_si;
}
